1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import android.app.ActivityManager; 20 import android.content.Context; 21 import android.os.UserHandle; 22 import android.provider.Settings; 23 import android.util.ArraySet; 24 import android.util.Slog; 25 import android.view.View; 26 import android.view.WindowManager; 27 import android.view.WindowManager.LayoutParams; 28 29 import java.io.PrintWriter; 30 import java.io.StringWriter; 31 32 /** 33 * Runtime adjustments applied to the global window policy. 34 * 35 * This includes forcing immersive mode behavior for one or both system bars (based on a package 36 * list) and permanently disabling immersive mode confirmations for specific packages. 37 * 38 * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs. 39 * e.g. 40 * to force immersive mode everywhere: 41 * "immersive.full=*" 42 * to force transient status for all apps except a specific package: 43 * "immersive.status=apps,-com.package" 44 * to disable the immersive mode confirmations for specific packages: 45 * "immersive.preconfirms=com.package.one,com.package.two" 46 * 47 * Separate multiple name-value pairs with ':' 48 * e.g. "immersive.status=apps:immersive.preconfirms=*" 49 */ 50 class PolicyControl { 51 private static final String TAG = "PolicyControl"; 52 private static final boolean DEBUG = false; 53 54 private static final String NAME_IMMERSIVE_FULL = "immersive.full"; 55 private static final String NAME_IMMERSIVE_STATUS = "immersive.status"; 56 private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation"; 57 private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms"; 58 59 private static String sSettingValue; 60 private static Filter sImmersivePreconfirmationsFilter; 61 private static Filter sImmersiveStatusFilter; 62 private static Filter sImmersiveNavigationFilter; 63 getSystemUiVisibility(WindowState win, LayoutParams attrs)64 static int getSystemUiVisibility(WindowState win, LayoutParams attrs) { 65 attrs = attrs != null ? attrs : win.getAttrs(); 66 int vis = win != null ? win.getSystemUiVisibility() 67 : (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility); 68 if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { 69 vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 70 | View.SYSTEM_UI_FLAG_FULLSCREEN; 71 if (attrs.isFullscreen()) { 72 vis |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; 73 } 74 vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 75 | View.STATUS_BAR_TRANSLUCENT); 76 } 77 if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { 78 vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 79 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 80 if (attrs.isFullscreen()) { 81 vis |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 82 } 83 vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 84 | View.NAVIGATION_BAR_TRANSLUCENT); 85 } 86 return vis; 87 } 88 getWindowFlags(WindowState win, LayoutParams attrs)89 static int getWindowFlags(WindowState win, LayoutParams attrs) { 90 attrs = attrs != null ? attrs : win.getAttrs(); 91 int flags = attrs.flags; 92 if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { 93 flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 94 flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 95 | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 96 } 97 if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { 98 flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 99 } 100 return flags; 101 } 102 adjustClearableFlags(WindowState win, int clearableFlags)103 static int adjustClearableFlags(WindowState win, int clearableFlags) { 104 final LayoutParams attrs = win != null ? win.getAttrs() : null; 105 if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { 106 clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; 107 } 108 return clearableFlags; 109 } 110 disableImmersiveConfirmation(String pkg)111 static boolean disableImmersiveConfirmation(String pkg) { 112 return (sImmersivePreconfirmationsFilter != null 113 && sImmersivePreconfirmationsFilter.matches(pkg)) 114 || ActivityManager.isRunningInTestHarness(); 115 } 116 reloadFromSetting(Context context)117 static boolean reloadFromSetting(Context context) { 118 if (DEBUG) Slog.d(TAG, "reloadFromSetting()"); 119 String value = null; 120 try { 121 value = Settings.Global.getStringForUser(context.getContentResolver(), 122 Settings.Global.POLICY_CONTROL, 123 UserHandle.USER_CURRENT); 124 if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) { 125 return false; 126 } 127 setFilters(value); 128 sSettingValue = value; 129 } catch (Throwable t) { 130 Slog.w(TAG, "Error loading policy control, value=" + value, t); 131 return false; 132 } 133 return true; 134 } 135 dump(String prefix, PrintWriter pw)136 static void dump(String prefix, PrintWriter pw) { 137 dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw); 138 dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw); 139 dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw); 140 } 141 dump(String name, Filter filter, String prefix, PrintWriter pw)142 private static void dump(String name, Filter filter, String prefix, PrintWriter pw) { 143 pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('='); 144 if (filter == null) { 145 pw.println("null"); 146 } else { 147 filter.dump(pw); pw.println(); 148 } 149 } 150 setFilters(String value)151 private static void setFilters(String value) { 152 if (DEBUG) Slog.d(TAG, "setFilters: " + value); 153 sImmersiveStatusFilter = null; 154 sImmersiveNavigationFilter = null; 155 sImmersivePreconfirmationsFilter = null; 156 if (value != null) { 157 String[] nvps = value.split(":"); 158 for (String nvp : nvps) { 159 int i = nvp.indexOf('='); 160 if (i == -1) continue; 161 String n = nvp.substring(0, i); 162 String v = nvp.substring(i + 1); 163 if (n.equals(NAME_IMMERSIVE_FULL)) { 164 Filter f = Filter.parse(v); 165 sImmersiveStatusFilter = sImmersiveNavigationFilter = f; 166 if (sImmersivePreconfirmationsFilter == null) { 167 sImmersivePreconfirmationsFilter = f; 168 } 169 } else if (n.equals(NAME_IMMERSIVE_STATUS)) { 170 Filter f = Filter.parse(v); 171 sImmersiveStatusFilter = f; 172 } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) { 173 Filter f = Filter.parse(v); 174 sImmersiveNavigationFilter = f; 175 if (sImmersivePreconfirmationsFilter == null) { 176 sImmersivePreconfirmationsFilter = f; 177 } 178 } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) { 179 Filter f = Filter.parse(v); 180 sImmersivePreconfirmationsFilter = f; 181 } 182 } 183 } 184 if (DEBUG) { 185 Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter); 186 Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter); 187 Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter); 188 } 189 } 190 191 private static class Filter { 192 private static final String ALL = "*"; 193 private static final String APPS = "apps"; 194 195 private final ArraySet<String> mWhitelist; 196 private final ArraySet<String> mBlacklist; 197 Filter(ArraySet<String> whitelist, ArraySet<String> blacklist)198 private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) { 199 mWhitelist = whitelist; 200 mBlacklist = blacklist; 201 } 202 matches(LayoutParams attrs)203 boolean matches(LayoutParams attrs) { 204 if (attrs == null) return false; 205 boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW 206 && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; 207 if (isApp && mBlacklist.contains(APPS)) return false; 208 if (onBlacklist(attrs.packageName)) return false; 209 if (isApp && mWhitelist.contains(APPS)) return true; 210 return onWhitelist(attrs.packageName); 211 } 212 matches(String packageName)213 boolean matches(String packageName) { 214 return !onBlacklist(packageName) && onWhitelist(packageName); 215 } 216 onBlacklist(String packageName)217 private boolean onBlacklist(String packageName) { 218 return mBlacklist.contains(packageName) || mBlacklist.contains(ALL); 219 } 220 onWhitelist(String packageName)221 private boolean onWhitelist(String packageName) { 222 return mWhitelist.contains(ALL) || mWhitelist.contains(packageName); 223 } 224 dump(PrintWriter pw)225 void dump(PrintWriter pw) { 226 pw.print("Filter["); 227 dump("whitelist", mWhitelist, pw); pw.print(','); 228 dump("blacklist", mBlacklist, pw); pw.print(']'); 229 } 230 dump(String name, ArraySet<String> set, PrintWriter pw)231 private void dump(String name, ArraySet<String> set, PrintWriter pw) { 232 pw.print(name); pw.print("=("); 233 final int n = set.size(); 234 for (int i = 0; i < n; i++) { 235 if (i > 0) pw.print(','); 236 pw.print(set.valueAt(i)); 237 } 238 pw.print(')'); 239 } 240 241 @Override toString()242 public String toString() { 243 StringWriter sw = new StringWriter(); 244 dump(new PrintWriter(sw, true)); 245 return sw.toString(); 246 } 247 248 // value = comma-delimited list of tokens, where token = (package name|apps|*) 249 // e.g. "com.package1", or "apps, com.android.keyguard" or "*" parse(String value)250 static Filter parse(String value) { 251 if (value == null) return null; 252 ArraySet<String> whitelist = new ArraySet<String>(); 253 ArraySet<String> blacklist = new ArraySet<String>(); 254 for (String token : value.split(",")) { 255 token = token.trim(); 256 if (token.startsWith("-") && token.length() > 1) { 257 token = token.substring(1); 258 blacklist.add(token); 259 } else { 260 whitelist.add(token); 261 } 262 } 263 return new Filter(whitelist, blacklist); 264 } 265 } 266 } 267