1 /* 2 * Copyright (C) 2020 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.systemui.wm; 18 19 import static com.android.systemui.car.Flags.packageLevelSystemBarVisibility; 20 21 import android.car.settings.CarSettings; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.os.Handler; 25 import android.os.UserHandle; 26 import android.provider.Settings; 27 import android.util.ArraySet; 28 import android.util.Slog; 29 import android.view.WindowInsets; 30 import android.view.WindowInsets.Type.InsetsType; 31 32 import androidx.annotation.VisibleForTesting; 33 34 import java.io.PrintWriter; 35 import java.io.StringWriter; 36 37 /** 38 * Util class to load PolicyControl and allow for querying if a package matches immersive filters. 39 * Similar to {@link com.android.server.wm.PolicyControl}, but separate due to CarSystemUI needing 40 * to set its own policies for system bar visibilities. 41 * 42 * This forces immersive mode behavior for one or both system bars (based on a package 43 * list). 44 * 45 * Control by setting {@link Settings.Global#POLICY_CONTROL_AUTO} to one or more name-value pairs. 46 * e.g. 47 * to force immersive mode everywhere: 48 * "immersive.full=*" 49 * to force hide status bars for com.package1 but not com.package2: 50 * "immersive.status=com.package1,-com.package2" 51 * to force hide navigation bar everywhere, and allow com.package1 to control visibility of both 52 * system bar types: 53 * "immersive.navigation=*,+com.package1" 54 * 55 * Separate multiple name-value pairs with ':' 56 * e.g. "immersive.status=com.package:immersive.navigation=*" 57 */ 58 public class BarControlPolicy { 59 60 private static final String TAG = "BarControlPolicy"; 61 private static final boolean DEBUG = false; 62 63 private static final String NAME_IMMERSIVE_FULL = "immersive.full"; 64 private static final String NAME_IMMERSIVE_STATUS = "immersive.status"; 65 private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation"; 66 67 @VisibleForTesting 68 static String sSettingValue; 69 @VisibleForTesting 70 static Filter sImmersiveStatusFilter; 71 private static Filter sImmersiveNavigationFilter; 72 73 /** Loads values from the POLICY_CONTROL setting to set filters. */ reloadFromSetting(Context context)74 static boolean reloadFromSetting(Context context) { 75 if (DEBUG) Slog.d(TAG, "reloadFromSetting()"); 76 String value = null; 77 try { 78 value = Settings.Global.getStringForUser(context.getContentResolver(), 79 CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE, 80 UserHandle.USER_CURRENT); 81 if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) { 82 return false; 83 } 84 setFilters(value); 85 sSettingValue = value; 86 } catch (Throwable t) { 87 Slog.w(TAG, "Error loading policy control, value=" + value, t); 88 return false; 89 } 90 return true; 91 } 92 93 /** Used in testing to reset BarControlPolicy. */ 94 @VisibleForTesting reset()95 static void reset() { 96 sSettingValue = null; 97 sImmersiveStatusFilter = null; 98 sImmersiveNavigationFilter = null; 99 } 100 101 /** 102 * Registers a content observer to listen to updates to the SYSTEM_BAR_VISIBILITY_OVERRIDE flag. 103 */ registerContentObserver(Context context, Handler handler, FilterListener listener)104 static void registerContentObserver(Context context, Handler handler, FilterListener listener) { 105 context.getContentResolver().registerContentObserver( 106 Settings.Global.getUriFor(CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE), false, 107 new ContentObserver(handler) { 108 @Override 109 public void onChange(boolean selfChange) { 110 if (reloadFromSetting(context)) { 111 listener.onFilterUpdated(); 112 } 113 } 114 }, UserHandle.USER_ALL); 115 } 116 117 /** 118 * Returns bar visibilities based on POLICY_CONTROL_AUTO filters and window policies. 119 * @return int[], where the first value is the inset types that should be shown, and the second 120 * is the inset types that should be hidden. 121 */ 122 @InsetsType getBarVisibilities(String packageName)123 public static int[] getBarVisibilities(String packageName) { 124 if (packageLevelSystemBarVisibility()) { 125 throw new IllegalStateException("This method should only be called when " 126 + "'package_level_system_bar_visibility' flag is disabled"); 127 } 128 129 int hideTypes = 0; 130 int showTypes = 0; 131 if (matchesStatusFilter(packageName)) { 132 hideTypes |= WindowInsets.Type.statusBars(); 133 } else { 134 showTypes |= WindowInsets.Type.statusBars(); 135 } 136 if (matchesNavigationFilter(packageName)) { 137 hideTypes |= WindowInsets.Type.navigationBars(); 138 } else { 139 showTypes |= WindowInsets.Type.navigationBars(); 140 } 141 142 return new int[] {showTypes, hideTypes}; 143 } 144 145 /** 146 * Returns bar visibilities based on POLICY_CONTROL_AUTO filters, window policies and the 147 * requested visible system bar types. 148 * 149 * @return int[], where the first value is the inset types that should be shown, and the second 150 * is the inset types that should be hidden. 151 */ 152 @InsetsType getBarVisibilities( String packageName, @InsetsType int requestedVisibleTypes)153 public static int[] getBarVisibilities( 154 String packageName, @InsetsType int requestedVisibleTypes) { 155 if (!packageLevelSystemBarVisibility()) { 156 throw new IllegalStateException("This method should only be called when " 157 + "'package_level_system_bar_visibility' flag is enabled"); 158 } 159 160 int hideTypes = 0; 161 int showTypes = 0; 162 163 boolean isStatusControlAllowed = sImmersiveStatusFilter != null 164 && sImmersiveStatusFilter.isControlAllowed(packageName); 165 166 if (isStatusControlAllowed) { 167 if ((requestedVisibleTypes & WindowInsets.Type.statusBars()) != 0) { 168 showTypes |= WindowInsets.Type.statusBars(); 169 } else { 170 hideTypes |= WindowInsets.Type.statusBars(); 171 } 172 } else if (matchesStatusFilter(packageName)) { 173 hideTypes |= WindowInsets.Type.statusBars(); 174 } else { 175 showTypes |= WindowInsets.Type.statusBars(); 176 } 177 178 boolean isNavigationControlAllowed = sImmersiveNavigationFilter != null 179 && sImmersiveNavigationFilter.isControlAllowed(packageName); 180 if (isNavigationControlAllowed) { 181 if ((requestedVisibleTypes & WindowInsets.Type.navigationBars()) != 0) { 182 showTypes |= WindowInsets.Type.navigationBars(); 183 } else { 184 hideTypes |= WindowInsets.Type.navigationBars(); 185 } 186 } else if (matchesNavigationFilter(packageName)) { 187 hideTypes |= WindowInsets.Type.navigationBars(); 188 } else { 189 showTypes |= WindowInsets.Type.navigationBars(); 190 } 191 192 return new int[] { showTypes, hideTypes }; 193 } 194 matchesStatusFilter(String packageName)195 private static boolean matchesStatusFilter(String packageName) { 196 return sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(packageName); 197 } 198 matchesNavigationFilter(String packageName)199 private static boolean matchesNavigationFilter(String packageName) { 200 return sImmersiveNavigationFilter != null 201 && sImmersiveNavigationFilter.matches(packageName); 202 } 203 setFilters(String value)204 private static void setFilters(String value) { 205 if (DEBUG) Slog.d(TAG, "setFilters: " + value); 206 sImmersiveStatusFilter = null; 207 sImmersiveNavigationFilter = null; 208 if (value != null) { 209 String[] nvps = value.split(":"); 210 for (String nvp : nvps) { 211 int i = nvp.indexOf('='); 212 if (i == -1) continue; 213 String n = nvp.substring(0, i); 214 String v = nvp.substring(i + 1); 215 if (n.equals(NAME_IMMERSIVE_FULL)) { 216 Filter f = Filter.parse(v); 217 sImmersiveStatusFilter = sImmersiveNavigationFilter = f; 218 } else if (n.equals(NAME_IMMERSIVE_STATUS)) { 219 Filter f = Filter.parse(v); 220 sImmersiveStatusFilter = f; 221 } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) { 222 Filter f = Filter.parse(v); 223 sImmersiveNavigationFilter = f; 224 } 225 } 226 } 227 if (DEBUG) { 228 Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter); 229 Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter); 230 } 231 } 232 233 private static class Filter { 234 private static final String ALL = "*"; 235 236 private final ArraySet<String> mToInclude; 237 private final ArraySet<String> mToExclude; 238 private final ArraySet<String> mAllowControl; 239 Filter(ArraySet<String> toInclude, ArraySet<String> toExclude, ArraySet<String> allowControl)240 private Filter(ArraySet<String> toInclude, ArraySet<String> toExclude, 241 ArraySet<String> allowControl) { 242 mToInclude = toInclude; 243 mToExclude = toExclude; 244 mAllowControl = packageLevelSystemBarVisibility() ? allowControl : null; 245 } 246 matches(String packageName)247 boolean matches(String packageName) { 248 if (packageName == null) return false; 249 if (toExclude(packageName)) return false; 250 return toInclude(packageName); 251 } 252 toExclude(String packageName)253 private boolean toExclude(String packageName) { 254 return mToExclude.contains(packageName) || mToExclude.contains(ALL); 255 } 256 toInclude(String packageName)257 private boolean toInclude(String packageName) { 258 return mToInclude.contains(ALL) || mToInclude.contains(packageName); 259 } 260 isControlAllowed(String packageName)261 boolean isControlAllowed(String packageName) { 262 return packageLevelSystemBarVisibility() && (mAllowControl.contains(ALL) 263 || mAllowControl.contains(packageName)); 264 } 265 dump(PrintWriter pw)266 void dump(PrintWriter pw) { 267 pw.print("Filter["); 268 dump("toInclude", mToInclude, pw); 269 270 pw.print(','); 271 dump("toExclude", mToExclude, pw); 272 273 if (packageLevelSystemBarVisibility()) { 274 pw.print(','); 275 dump("allowControl", mAllowControl, pw); 276 } 277 278 pw.print(']'); 279 } 280 dump(String name, ArraySet<String> set, PrintWriter pw)281 private void dump(String name, ArraySet<String> set, PrintWriter pw) { 282 pw.print(name); pw.print("=("); 283 int n = set.size(); 284 for (int i = 0; i < n; i++) { 285 if (i > 0) pw.print(','); 286 pw.print(set.valueAt(i)); 287 } 288 pw.print(')'); 289 } 290 291 @Override toString()292 public String toString() { 293 StringWriter sw = new StringWriter(); 294 dump(new PrintWriter(sw, true)); 295 return sw.toString(); 296 } 297 298 // value = comma-delimited list of tokens, where token = (package name|*) 299 // e.g. "com.package1", or "com.android.systemui, com.android.keyguard" or "*" parse(String value)300 static Filter parse(String value) { 301 if (value == null) return null; 302 ArraySet<String> toInclude = new ArraySet<>(); 303 ArraySet<String> toExclude = new ArraySet<>(); 304 ArraySet<String> allowControl = 305 packageLevelSystemBarVisibility() ? new ArraySet<>() : null; 306 for (String token : value.split(",")) { 307 token = token.trim(); 308 if (token.startsWith("-") && token.length() > 1) { 309 token = token.substring(1); 310 toExclude.add(token); 311 } else if (allowControl != null && token.startsWith("+") && token.length() > 1) { 312 token = token.substring(1); 313 allowControl.add(token); 314 } else { 315 toInclude.add(token); 316 } 317 } 318 return new Filter(toInclude, toExclude, allowControl); 319 } 320 } 321 322 /** 323 * Interface to listen for updates to the filter triggered by the content observer listening to 324 * the SYSTEM_BAR_VISIBILITY_OVERRIDE flag. 325 */ 326 interface FilterListener { 327 328 /** Callback triggered when the content observer updates the filter. */ onFilterUpdated()329 void onFilterUpdated(); 330 } 331 BarControlPolicy()332 private BarControlPolicy() {} 333 } 334