• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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