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