• 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 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