1 /*
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 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.app.Activity.RESULT_CANCELED;
20 import static android.app.Activity.RESULT_OK;
21 import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
22 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
23 import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
24 import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
25 import static android.os.UserHandle.USER_CURRENT;
26 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
27 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
28 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
29 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
30 
31 import static com.android.systemui.shared.system.QuickStepContract.ACTION_ENABLE_GESTURE_NAV;
32 import static com.android.systemui.shared.system.QuickStepContract.ACTION_ENABLE_GESTURE_NAV_RESULT;
33 import static com.android.systemui.shared.system.QuickStepContract.EXTRA_RESULT_INTENT;
34 
35 import android.app.PendingIntent;
36 import android.app.admin.DevicePolicyManager;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.om.IOverlayManager;
43 import android.content.pm.PackageManager;
44 import android.content.res.ApkAssets;
45 import android.os.PatternMatcher;
46 import android.os.RemoteException;
47 import android.os.ServiceManager;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.provider.Settings;
51 import android.provider.Settings.Secure;
52 import android.util.Log;
53 import android.util.SparseBooleanArray;
54 
55 import com.android.systemui.Dumpable;
56 import com.android.systemui.UiOffloadThread;
57 import com.android.systemui.shared.system.ActivityManagerWrapper;
58 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
59 
60 import java.io.FileDescriptor;
61 import java.io.PrintWriter;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 
65 import javax.inject.Inject;
66 import javax.inject.Singleton;
67 
68 /**
69  * Controller for tracking the current navigation bar mode.
70  */
71 @Singleton
72 public class NavigationModeController implements Dumpable {
73 
74     private static final String TAG = NavigationModeController.class.getSimpleName();
75     private static final boolean DEBUG = false;
76 
77     public interface ModeChangedListener {
onNavigationModeChanged(int mode)78         void onNavigationModeChanged(int mode);
79     }
80 
81     private final Context mContext;
82     private Context mCurrentUserContext;
83     private final IOverlayManager mOverlayManager;
84     private final DeviceProvisionedController mDeviceProvisionedController;
85     private final UiOffloadThread mUiOffloadThread;
86 
87     private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
88 
89     private int mMode = NAV_BAR_MODE_3BUTTON;
90     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
91 
92     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
93         @Override
94         public void onReceive(Context context, Intent intent) {
95             switch (intent.getAction()) {
96                 case ACTION_OVERLAY_CHANGED:
97                     if (DEBUG) {
98                         Log.d(TAG, "ACTION_OVERLAY_CHANGED");
99                     }
100                     updateCurrentInteractionMode(true /* notify */);
101                     break;
102             }
103         }
104     };
105 
106     private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
107             new DeviceProvisionedController.DeviceProvisionedListener() {
108                 @Override
109                 public void onDeviceProvisionedChanged() {
110                     if (DEBUG) {
111                         Log.d(TAG, "onDeviceProvisionedChanged: "
112                                 + mDeviceProvisionedController.isDeviceProvisioned());
113                     }
114                     // Once the device has been provisioned, check if we can restore gestural nav
115                     restoreGesturalNavOverlayIfNecessary();
116                 }
117 
118                 @Override
119                 public void onUserSetupChanged() {
120                     if (DEBUG) {
121                         Log.d(TAG, "onUserSetupChanged: "
122                                 + mDeviceProvisionedController.isCurrentUserSetup());
123                     }
124                     // Once the user has been setup, check if we can restore gestural nav
125                     restoreGesturalNavOverlayIfNecessary();
126                 }
127 
128                 @Override
129                 public void onUserSwitched() {
130                     if (DEBUG) {
131                         Log.d(TAG, "onUserSwitched: "
132                                 + ActivityManagerWrapper.getInstance().getCurrentUserId());
133                     }
134 
135                     // Update the nav mode for the current user
136                     updateCurrentInteractionMode(true /* notify */);
137 
138                     // When switching users, defer enabling the gestural nav overlay until the user
139                     // is all set up
140                     deferGesturalNavOverlayIfNecessary();
141                 }
142             };
143 
144     private BroadcastReceiver mEnableGestureNavReceiver;
145 
146     @Inject
NavigationModeController(Context context, DeviceProvisionedController deviceProvisionedController, UiOffloadThread uiOffloadThread)147     public NavigationModeController(Context context,
148             DeviceProvisionedController deviceProvisionedController,
149             UiOffloadThread uiOffloadThread) {
150         mContext = context;
151         mCurrentUserContext = context;
152         mOverlayManager = IOverlayManager.Stub.asInterface(
153                 ServiceManager.getService(Context.OVERLAY_SERVICE));
154         mUiOffloadThread = uiOffloadThread;
155         mDeviceProvisionedController = deviceProvisionedController;
156         mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
157 
158         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
159         overlayFilter.addDataScheme("package");
160         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
161         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
162 
163         IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
164         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
165                 null);
166 
167         updateCurrentInteractionMode(false /* notify */);
168 
169         // Check if we need to defer enabling gestural nav
170         deferGesturalNavOverlayIfNecessary();
171     }
172 
removeEnableGestureNavListener()173     private void removeEnableGestureNavListener() {
174         if (mEnableGestureNavReceiver != null) {
175             if (DEBUG) {
176                 Log.d(TAG, "mEnableGestureNavReceiver unregistered");
177             }
178             mContext.unregisterReceiver(mEnableGestureNavReceiver);
179             mEnableGestureNavReceiver = null;
180         }
181     }
182 
setGestureModeOverlayForMainLauncher()183     private boolean setGestureModeOverlayForMainLauncher() {
184         removeEnableGestureNavListener();
185         if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
186             // Already in gesture mode
187             return true;
188         }
189 
190         Log.d(TAG, "Switching system navigation to full-gesture mode:"
191                 + " contextUser="
192                 + mCurrentUserContext.getUserId());
193 
194         setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
195         return true;
196     }
197 
enableGestureNav(Intent intent)198     private boolean enableGestureNav(Intent intent) {
199         if (!(intent.getParcelableExtra(EXTRA_RESULT_INTENT) instanceof PendingIntent)) {
200             Log.e(TAG, "No callback pending intent was attached");
201             return false;
202         }
203 
204         PendingIntent callback = intent.getParcelableExtra(EXTRA_RESULT_INTENT);
205         Intent callbackIntent = callback.getIntent();
206         if (callbackIntent == null
207                 || !ACTION_ENABLE_GESTURE_NAV_RESULT.equals(callbackIntent.getAction())) {
208             Log.e(TAG, "Invalid callback intent");
209             return false;
210         }
211         String callerPackage = callback.getCreatorPackage();
212         UserHandle callerUser = callback.getCreatorUserHandle();
213 
214         DevicePolicyManager dpm = mCurrentUserContext.getSystemService(DevicePolicyManager.class);
215         ComponentName ownerComponent = dpm.getDeviceOwnerComponentOnCallingUser();
216 
217         if (ownerComponent != null) {
218             // Verify that the caller is the owner component
219             if (!ownerComponent.getPackageName().equals(callerPackage)
220                     || !mCurrentUserContext.getUser().equals(callerUser)) {
221                 Log.e(TAG, "Callback must be from the device owner");
222                 return false;
223             }
224         } else {
225             UserHandle callerParent = mCurrentUserContext.getSystemService(UserManager.class)
226                     .getProfileParent(callerUser);
227             if (callerParent == null || !callerParent.equals(mCurrentUserContext.getUser())) {
228                 Log.e(TAG, "Callback must be from a managed user");
229                 return false;
230             }
231             ComponentName profileOwner = dpm.getProfileOwnerAsUser(callerUser);
232             if (profileOwner == null || !profileOwner.getPackageName().equals(callerPackage)) {
233                 Log.e(TAG, "Callback must be from the profile owner");
234                 return false;
235             }
236         }
237 
238         return setGestureModeOverlayForMainLauncher();
239     }
240 
updateCurrentInteractionMode(boolean notify)241     public void updateCurrentInteractionMode(boolean notify) {
242         mCurrentUserContext = getCurrentUserContext();
243         int mode = getCurrentInteractionMode(mCurrentUserContext);
244         mMode = mode;
245         mUiOffloadThread.submit(() -> {
246             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
247                     Secure.NAVIGATION_MODE, String.valueOf(mode));
248         });
249         if (DEBUG) {
250             Log.e(TAG, "updateCurrentInteractionMode: mode=" + mMode
251                     + " contextUser=" + mCurrentUserContext.getUserId());
252             dumpAssetPaths(mCurrentUserContext);
253         }
254 
255         if (notify) {
256             for (int i = 0; i < mListeners.size(); i++) {
257                 mListeners.get(i).onNavigationModeChanged(mode);
258             }
259         }
260     }
261 
addListener(ModeChangedListener listener)262     public int addListener(ModeChangedListener listener) {
263         mListeners.add(listener);
264         return getCurrentInteractionMode(mCurrentUserContext);
265     }
266 
removeListener(ModeChangedListener listener)267     public void removeListener(ModeChangedListener listener) {
268         mListeners.remove(listener);
269     }
270 
getCurrentInteractionMode(Context context)271     private int getCurrentInteractionMode(Context context) {
272         int mode = context.getResources().getInteger(
273                 com.android.internal.R.integer.config_navBarInteractionMode);
274         if (DEBUG) {
275             Log.d(TAG, "getCurrentInteractionMode: mode=" + mMode
276                     + " contextUser=" + context.getUserId());
277         }
278         return mode;
279     }
280 
getCurrentUserContext()281     public Context getCurrentUserContext() {
282         int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
283         if (DEBUG) {
284             Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
285                     + " currentUser=" + userId);
286         }
287         if (mContext.getUserId() == userId) {
288             return mContext;
289         }
290         try {
291             return mContext.createPackageContextAsUser(mContext.getPackageName(),
292                     0 /* flags */, UserHandle.of(userId));
293         } catch (PackageManager.NameNotFoundException e) {
294             // Never happens for the sysui package
295             return null;
296         }
297     }
298 
supportsDeviceAdmin()299     private boolean supportsDeviceAdmin() {
300         return mContext.getPackageManager().hasSystemFeature(FEATURE_DEVICE_ADMIN);
301     }
302 
deferGesturalNavOverlayIfNecessary()303     private void deferGesturalNavOverlayIfNecessary() {
304         final int userId = mDeviceProvisionedController.getCurrentUser();
305         mRestoreGesturalNavBarMode.put(userId, false);
306         if (mDeviceProvisionedController.isDeviceProvisioned()
307                 && mDeviceProvisionedController.isCurrentUserSetup()) {
308             // User is already setup and device is provisioned, nothing to do
309             if (DEBUG) {
310                 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
311                         + "setup");
312             }
313             removeEnableGestureNavListener();
314             return;
315         }
316 
317         ArrayList<String> defaultOverlays = new ArrayList<>();
318         try {
319             defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
320         } catch (RemoteException e) {
321             Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
322         }
323         if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
324             // No default gesture nav overlay
325             if (DEBUG) {
326                 Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
327                         + "default=" + defaultOverlays);
328             }
329             removeEnableGestureNavListener();
330             return;
331         }
332 
333         // If the default is gestural, force-enable three button mode until the device is
334         // provisioned
335         setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
336         mRestoreGesturalNavBarMode.put(userId, true);
337 
338         if (supportsDeviceAdmin() && mEnableGestureNavReceiver == null) {
339             mEnableGestureNavReceiver = new BroadcastReceiver() {
340                 @Override
341                 public void onReceive(Context context, Intent intent) {
342                     if (DEBUG) {
343                         Log.d(TAG, "ACTION_ENABLE_GESTURE_NAV");
344                     }
345                     setResultCode(enableGestureNav(intent) ? RESULT_OK : RESULT_CANCELED);
346                 }
347             };
348             // Register for all users so that we can get managed users as well
349             mContext.registerReceiverAsUser(mEnableGestureNavReceiver, UserHandle.ALL,
350                     new IntentFilter(ACTION_ENABLE_GESTURE_NAV), null, null);
351             if (DEBUG) {
352                 Log.d(TAG, "mEnableGestureNavReceiver registered");
353             }
354         }
355         if (DEBUG) {
356             Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
357         }
358     }
359 
restoreGesturalNavOverlayIfNecessary()360     private void restoreGesturalNavOverlayIfNecessary() {
361         if (DEBUG) {
362             Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
363                     + mRestoreGesturalNavBarMode);
364         }
365         final int userId = mDeviceProvisionedController.getCurrentUser();
366         if (mRestoreGesturalNavBarMode.get(userId)) {
367             // Restore the gestural state if necessary
368             if (!supportsDeviceAdmin()
369                     || mCurrentUserContext.getSystemService(DevicePolicyManager.class)
370                     .getUserProvisioningState() == STATE_USER_UNMANAGED) {
371                 setGestureModeOverlayForMainLauncher();
372             } else {
373                 if (DEBUG) {
374                     Log.d(TAG, "Not restoring to gesture nav for managed user");
375                 }
376             }
377             mRestoreGesturalNavBarMode.put(userId, false);
378         }
379     }
380 
setModeOverlay(String overlayPkg, int userId)381     public void setModeOverlay(String overlayPkg, int userId) {
382         mUiOffloadThread.submit(() -> {
383             try {
384                 mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
385                 if (DEBUG) {
386                     Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg
387                             + " userId=" + userId);
388                 }
389             } catch (RemoteException e) {
390                 Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
391             }
392         });
393     }
394 
395     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)396     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
397         pw.println("NavigationModeController:");
398         pw.println("  mode=" + mMode);
399         String defaultOverlays = "";
400         try {
401             defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages());
402         } catch (RemoteException e) {
403             defaultOverlays = "failed_to_fetch";
404         }
405         pw.println("  defaultOverlays=" + defaultOverlays);
406         dumpAssetPaths(mCurrentUserContext);
407     }
408 
dumpAssetPaths(Context context)409     private void dumpAssetPaths(Context context) {
410         Log.d(TAG, "assetPaths=");
411         ApkAssets[] assets = context.getResources().getAssets().getApkAssets();
412         for (ApkAssets a : assets) {
413             Log.d(TAG, "    " + a.getAssetPath());
414         }
415     }
416 }
417