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