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