blob: 805a6e547793d209a686d902368f01ce91971839 [file] [log] [blame]
Winson Chung1aa24b92019-04-24 15:17:33 -07001/*
2 * Copyright (C) 2019 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
17package com.android.systemui.statusbar.phone;
18
19import static android.content.Intent.ACTION_OVERLAY_CHANGED;
Winson Chungfacf2132019-04-15 17:39:49 -070020import static android.os.UserHandle.USER_CURRENT;
Winson Chung1aa24b92019-04-24 15:17:33 -070021import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
Winson Chungfacf2132019-04-15 17:39:49 -070022import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
23import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
Winson Chung1aa24b92019-04-24 15:17:33 -070024
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.om.IOverlayManager;
30import android.content.pm.PackageManager;
31import android.content.res.ApkAssets;
32import android.os.PatternMatcher;
Winson Chungfacf2132019-04-15 17:39:49 -070033import android.os.RemoteException;
Winson Chung1aa24b92019-04-24 15:17:33 -070034import android.os.ServiceManager;
35import android.os.UserHandle;
Hyunyoung Song83972392019-05-15 22:56:41 -070036import android.provider.Settings;
37import android.provider.Settings.Secure;
Winson Chung1aa24b92019-04-24 15:17:33 -070038import android.util.Log;
Winson Chungfacf2132019-04-15 17:39:49 -070039import android.util.SparseBooleanArray;
Winson Chung1aa24b92019-04-24 15:17:33 -070040
41import com.android.systemui.Dumpable;
Winson Chungfacf2132019-04-15 17:39:49 -070042import com.android.systemui.UiOffloadThread;
Winson Chung1aa24b92019-04-24 15:17:33 -070043import com.android.systemui.shared.system.ActivityManagerWrapper;
Winson Chungfacf2132019-04-15 17:39:49 -070044import com.android.systemui.statusbar.policy.DeviceProvisionedController;
Winson Chung1aa24b92019-04-24 15:17:33 -070045
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.util.ArrayList;
Winson Chungfacf2132019-04-15 17:39:49 -070049import java.util.Arrays;
Winson Chung1aa24b92019-04-24 15:17:33 -070050
51import javax.inject.Inject;
52import javax.inject.Singleton;
53
54/**
55 * Controller for tracking the current navigation bar mode.
56 */
57@Singleton
58public class NavigationModeController implements Dumpable {
59
Winson Chungfacf2132019-04-15 17:39:49 -070060 private static final String TAG = NavigationModeController.class.getSimpleName();
Winson Chung1aa24b92019-04-24 15:17:33 -070061 private static final boolean DEBUG = true;
62
63 public interface ModeChangedListener {
64 void onNavigationModeChanged(int mode);
65 }
66
67 private final Context mContext;
Winson Chung1bb74552019-05-15 16:09:22 -070068 private Context mCurrentUserContext;
Winson Chung1aa24b92019-04-24 15:17:33 -070069 private final IOverlayManager mOverlayManager;
Winson Chungfacf2132019-04-15 17:39:49 -070070 private final DeviceProvisionedController mDeviceProvisionedController;
71 private final UiOffloadThread mUiOffloadThread;
72
73 private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
Winson Chung1aa24b92019-04-24 15:17:33 -070074
75 private int mMode = NAV_BAR_MODE_3BUTTON;
76 private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
77
78 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
79 @Override
80 public void onReceive(Context context, Intent intent) {
Winson Chungfacf2132019-04-15 17:39:49 -070081 if (intent.getAction().equals(ACTION_OVERLAY_CHANGED)) {
82 if (DEBUG) {
83 Log.d(TAG, "ACTION_OVERLAY_CHANGED");
84 }
85 updateCurrentInteractionMode(true /* notify */);
Winson Chung1aa24b92019-04-24 15:17:33 -070086 }
87 }
88 };
89
Winson Chungfacf2132019-04-15 17:39:49 -070090 private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
91 new DeviceProvisionedController.DeviceProvisionedListener() {
92 @Override
93 public void onDeviceProvisionedChanged() {
94 if (DEBUG) {
95 Log.d(TAG, "onDeviceProvisionedChanged: "
96 + mDeviceProvisionedController.isDeviceProvisioned());
97 }
98 // Once the device has been provisioned, check if we can restore gestural nav
99 restoreGesturalNavOverlayIfNecessary();
100 }
101
102 @Override
103 public void onUserSetupChanged() {
104 if (DEBUG) {
105 Log.d(TAG, "onUserSetupChanged: "
106 + mDeviceProvisionedController.isCurrentUserSetup());
107 }
108 // Once the user has been setup, check if we can restore gestural nav
109 restoreGesturalNavOverlayIfNecessary();
110 }
111
112 @Override
113 public void onUserSwitched() {
114 if (DEBUG) {
115 Log.d(TAG, "onUserSwitched: "
116 + ActivityManagerWrapper.getInstance().getCurrentUserId());
117 }
118
119 // Update the nav mode for the current user
120 updateCurrentInteractionMode(true /* notify */);
121
122 // When switching users, defer enabling the gestural nav overlay until the user
123 // is all set up
124 deferGesturalNavOverlayIfNecessary();
125 }
126 };
127
Winson Chung1aa24b92019-04-24 15:17:33 -0700128 @Inject
Winson Chungfacf2132019-04-15 17:39:49 -0700129 public NavigationModeController(Context context,
130 DeviceProvisionedController deviceProvisionedController,
131 UiOffloadThread uiOffloadThread) {
Winson Chung1aa24b92019-04-24 15:17:33 -0700132 mContext = context;
Winson Chung1bb74552019-05-15 16:09:22 -0700133 mCurrentUserContext = context;
Winson Chung1aa24b92019-04-24 15:17:33 -0700134 mOverlayManager = IOverlayManager.Stub.asInterface(
135 ServiceManager.getService(Context.OVERLAY_SERVICE));
Winson Chungfacf2132019-04-15 17:39:49 -0700136 mUiOffloadThread = uiOffloadThread;
137 mDeviceProvisionedController = deviceProvisionedController;
138 mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
Winson Chung1aa24b92019-04-24 15:17:33 -0700139
140 IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
141 overlayFilter.addDataScheme("package");
142 overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
143 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
144
Winson Chungfacf2132019-04-15 17:39:49 -0700145 updateCurrentInteractionMode(false /* notify */);
Winson Chung1aa24b92019-04-24 15:17:33 -0700146
Winson Chungfacf2132019-04-15 17:39:49 -0700147 // Check if we need to defer enabling gestural nav
148 deferGesturalNavOverlayIfNecessary();
149 }
150
151 public void updateCurrentInteractionMode(boolean notify) {
Winson Chung1bb74552019-05-15 16:09:22 -0700152 mCurrentUserContext = getCurrentUserContext();
153 int mode = getCurrentInteractionMode(mCurrentUserContext);
Winson Chungfacf2132019-04-15 17:39:49 -0700154 mMode = mode;
Hyunyoung Song83972392019-05-15 22:56:41 -0700155 mUiOffloadThread.submit(() -> {
156 Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
157 Secure.NAVIGATION_MODE, String.valueOf(mode));
158 });
Winson Chungfacf2132019-04-15 17:39:49 -0700159 if (DEBUG) {
160 Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode
Winson Chung1bb74552019-05-15 16:09:22 -0700161 + " contextUser=" + mCurrentUserContext.getUserId());
162 dumpAssetPaths(mCurrentUserContext);
Winson Chungfacf2132019-04-15 17:39:49 -0700163 }
164
165 if (notify) {
166 for (int i = 0; i < mListeners.size(); i++) {
167 mListeners.get(i).onNavigationModeChanged(mode);
168 }
169 }
Winson Chung1aa24b92019-04-24 15:17:33 -0700170 }
171
172 public int addListener(ModeChangedListener listener) {
173 mListeners.add(listener);
Winson Chung1bb74552019-05-15 16:09:22 -0700174 return getCurrentInteractionMode(mCurrentUserContext);
Winson Chung1aa24b92019-04-24 15:17:33 -0700175 }
176
177 public void removeListener(ModeChangedListener listener) {
178 mListeners.remove(listener);
179 }
180
181 private int getCurrentInteractionMode(Context context) {
182 int mode = context.getResources().getInteger(
183 com.android.internal.R.integer.config_navBarInteractionMode);
184 return mode;
185 }
186
187 private Context getCurrentUserContext() {
188 int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
189 if (DEBUG) {
190 Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
191 + " currentUser=" + userId);
192 }
193 if (mContext.getUserId() == userId) {
194 return mContext;
195 }
196 try {
197 return mContext.createPackageContextAsUser(mContext.getPackageName(),
198 0 /* flags */, UserHandle.of(userId));
199 } catch (PackageManager.NameNotFoundException e) {
200 // Never happens for the sysui package
201 return null;
202 }
203 }
204
Winson Chungfacf2132019-04-15 17:39:49 -0700205 private void deferGesturalNavOverlayIfNecessary() {
206 final int userId = mDeviceProvisionedController.getCurrentUser();
207 mRestoreGesturalNavBarMode.put(userId, false);
208 if (mDeviceProvisionedController.isDeviceProvisioned()
209 && mDeviceProvisionedController.isCurrentUserSetup()) {
210 // User is already setup and device is provisioned, nothing to do
211 if (DEBUG) {
212 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
213 + "setup");
214 }
215 return;
216 }
217
218 ArrayList<String> defaultOverlays = new ArrayList<>();
219 try {
220 defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
221 } catch (RemoteException e) {
222 Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
223 }
224 if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
225 // No default gesture nav overlay
226 if (DEBUG) {
227 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
228 + "default=" + defaultOverlays);
229 }
230 return;
231 }
232
233 // If the default is gestural, force-enable three button mode until the device is
234 // provisioned
235 setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
236 mRestoreGesturalNavBarMode.put(userId, true);
237 if (DEBUG) {
238 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
239 }
240 }
241
242 private void restoreGesturalNavOverlayIfNecessary() {
243 if (DEBUG) {
244 Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
245 + mRestoreGesturalNavBarMode);
246 }
247 final int userId = mDeviceProvisionedController.getCurrentUser();
248 if (mRestoreGesturalNavBarMode.get(userId)) {
249 // Restore the gestural state if necessary
250 setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
251 mRestoreGesturalNavBarMode.put(userId, false);
252 }
253 }
254
255 public void setModeOverlay(String overlayPkg, int userId) {
256 mUiOffloadThread.submit(() -> {
257 try {
258 mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
259 } catch (RemoteException e) {
260 Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
261 }
262 });
263 }
264
Winson Chung1aa24b92019-04-24 15:17:33 -0700265 @Override
266 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
267 pw.println("NavigationModeController:");
268 pw.println(" mode=" + mMode);
Winson Chungfacf2132019-04-15 17:39:49 -0700269 String defaultOverlays = "";
270 try {
271 defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages());
272 } catch (RemoteException e) {
273 defaultOverlays = "failed_to_fetch";
274 }
275 pw.println(" defaultOverlays=" + defaultOverlays);
Winson Chung1bb74552019-05-15 16:09:22 -0700276 dumpAssetPaths(mCurrentUserContext);
Winson Chung1aa24b92019-04-24 15:17:33 -0700277 }
278
279 private void dumpAssetPaths(Context context) {
280 Log.d(TAG, "assetPaths=");
281 ApkAssets[] assets = context.getResources().getAssets().getApkAssets();
282 for (ApkAssets a : assets) {
283 Log.d(TAG, " " + a.getAssetPath());
284 }
285 }
286}