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.content.Intent.ACTION_OVERLAY_CHANGED; 20 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; 21 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; 22 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.om.IOverlayManager; 28 import android.content.om.OverlayInfo; 29 import android.content.pm.PackageManager; 30 import android.content.res.ApkAssets; 31 import android.os.PatternMatcher; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.provider.Settings.Secure; 37 import android.util.Log; 38 39 import com.android.systemui.Dumpable; 40 import com.android.systemui.dagger.qualifiers.UiBackground; 41 import com.android.systemui.shared.system.ActivityManagerWrapper; 42 import com.android.systemui.statusbar.policy.ConfigurationController; 43 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 44 45 import java.io.FileDescriptor; 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.concurrent.Executor; 49 50 import javax.inject.Inject; 51 import javax.inject.Singleton; 52 53 /** 54 * Controller for tracking the current navigation bar mode. 55 */ 56 @Singleton 57 public class NavigationModeController implements Dumpable { 58 59 private static final String TAG = NavigationModeController.class.getSimpleName(); 60 private static final boolean DEBUG = true; 61 62 public interface ModeChangedListener { onNavigationModeChanged(int mode)63 void onNavigationModeChanged(int mode); 64 } 65 66 private final Context mContext; 67 private Context mCurrentUserContext; 68 private final IOverlayManager mOverlayManager; 69 private final Executor mUiBgExecutor; 70 71 private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); 72 73 private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback = 74 new DeviceProvisionedController.DeviceProvisionedListener() { 75 @Override 76 public void onUserSwitched() { 77 if (DEBUG) { 78 Log.d(TAG, "onUserSwitched: " 79 + ActivityManagerWrapper.getInstance().getCurrentUserId()); 80 } 81 82 // Update the nav mode for the current user 83 updateCurrentInteractionMode(true /* notify */); 84 } 85 }; 86 87 // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for 88 // the secondary user (b/158613864), so we need to update the interaction mode here as well 89 // as a fallback if we don't receive the configuration change 90 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 91 @Override 92 public void onReceive(Context context, Intent intent) { 93 if (DEBUG) { 94 Log.d(TAG, "ACTION_OVERLAY_CHANGED"); 95 } 96 updateCurrentInteractionMode(true /* notify */); 97 } 98 }; 99 100 101 @Inject NavigationModeController(Context context, DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, @UiBackground Executor uiBgExecutor)102 public NavigationModeController(Context context, 103 DeviceProvisionedController deviceProvisionedController, 104 ConfigurationController configurationController, 105 @UiBackground Executor uiBgExecutor) { 106 mContext = context; 107 mCurrentUserContext = context; 108 mOverlayManager = IOverlayManager.Stub.asInterface( 109 ServiceManager.getService(Context.OVERLAY_SERVICE)); 110 mUiBgExecutor = uiBgExecutor; 111 deviceProvisionedController.addCallback(mDeviceProvisionedCallback); 112 113 IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); 114 overlayFilter.addDataScheme("package"); 115 overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); 116 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); 117 118 configurationController.addCallback(new ConfigurationController.ConfigurationListener() { 119 @Override 120 public void onOverlayChanged() { 121 if (DEBUG) { 122 Log.d(TAG, "onOverlayChanged"); 123 } 124 updateCurrentInteractionMode(true /* notify */); 125 } 126 }); 127 128 updateCurrentInteractionMode(false /* notify */); 129 } 130 updateCurrentInteractionMode(boolean notify)131 public void updateCurrentInteractionMode(boolean notify) { 132 mCurrentUserContext = getCurrentUserContext(); 133 int mode = getCurrentInteractionMode(mCurrentUserContext); 134 if (mode == NAV_BAR_MODE_GESTURAL) { 135 switchToDefaultGestureNavOverlayIfNecessary(); 136 } 137 mUiBgExecutor.execute(() -> 138 Settings.Secure.putString(mCurrentUserContext.getContentResolver(), 139 Secure.NAVIGATION_MODE, String.valueOf(mode))); 140 if (DEBUG) { 141 Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode); 142 dumpAssetPaths(mCurrentUserContext); 143 } 144 145 if (notify) { 146 for (int i = 0; i < mListeners.size(); i++) { 147 mListeners.get(i).onNavigationModeChanged(mode); 148 } 149 } 150 } 151 addListener(ModeChangedListener listener)152 public int addListener(ModeChangedListener listener) { 153 mListeners.add(listener); 154 return getCurrentInteractionMode(mCurrentUserContext); 155 } 156 removeListener(ModeChangedListener listener)157 public void removeListener(ModeChangedListener listener) { 158 mListeners.remove(listener); 159 } 160 getCurrentInteractionMode(Context context)161 private int getCurrentInteractionMode(Context context) { 162 int mode = context.getResources().getInteger( 163 com.android.internal.R.integer.config_navBarInteractionMode); 164 if (DEBUG) { 165 Log.d(TAG, "getCurrentInteractionMode: mode=" + mode 166 + " contextUser=" + context.getUserId()); 167 } 168 return mode; 169 } 170 getCurrentUserContext()171 public Context getCurrentUserContext() { 172 int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); 173 if (DEBUG) { 174 Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId() 175 + " currentUser=" + userId); 176 } 177 if (mContext.getUserId() == userId) { 178 return mContext; 179 } 180 try { 181 return mContext.createPackageContextAsUser(mContext.getPackageName(), 182 0 /* flags */, UserHandle.of(userId)); 183 } catch (PackageManager.NameNotFoundException e) { 184 // Never happens for the sysui package 185 Log.e(TAG, "Failed to create package context", e); 186 return null; 187 } 188 } 189 switchToDefaultGestureNavOverlayIfNecessary()190 private void switchToDefaultGestureNavOverlayIfNecessary() { 191 final int userId = mCurrentUserContext.getUserId(); 192 try { 193 final IOverlayManager om = IOverlayManager.Stub.asInterface( 194 ServiceManager.getService(Context.OVERLAY_SERVICE)); 195 final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); 196 if (info != null && !info.isEnabled()) { 197 // Enable the default gesture nav overlay, and move the back gesture inset scale to 198 // Settings.Secure for left and right sensitivity. 199 final int curInset = mCurrentUserContext.getResources().getDimensionPixelSize( 200 com.android.internal.R.dimen.config_backGestureInset); 201 om.setEnabledExclusiveInCategory(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); 202 final int defInset = mCurrentUserContext.getResources().getDimensionPixelSize( 203 com.android.internal.R.dimen.config_backGestureInset); 204 205 final float scale = defInset == 0 ? 1.0f : ((float) curInset) / defInset; 206 Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(), 207 Secure.BACK_GESTURE_INSET_SCALE_LEFT, scale); 208 Settings.Secure.putFloat(mCurrentUserContext.getContentResolver(), 209 Secure.BACK_GESTURE_INSET_SCALE_RIGHT, scale); 210 if (DEBUG) { 211 Log.v(TAG, "Moved back sensitivity for user " + userId + " to scale " + scale); 212 } 213 } 214 } catch (SecurityException | IllegalStateException | RemoteException e) { 215 Log.e(TAG, "Failed to switch to default gesture nav overlay for user " + userId); 216 } 217 } 218 219 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)220 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 221 pw.println("NavigationModeController:"); 222 pw.println(" mode=" + getCurrentInteractionMode(mCurrentUserContext)); 223 String defaultOverlays = ""; 224 try { 225 defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages()); 226 } catch (RemoteException e) { 227 defaultOverlays = "failed_to_fetch"; 228 } 229 pw.println(" defaultOverlays=" + defaultOverlays); 230 dumpAssetPaths(mCurrentUserContext); 231 } 232 dumpAssetPaths(Context context)233 private void dumpAssetPaths(Context context) { 234 Log.d(TAG, " contextUser=" + mCurrentUserContext.getUserId()); 235 Log.d(TAG, " assetPaths="); 236 ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); 237 for (ApkAssets a : assets) { 238 Log.d(TAG, " " + a.getAssetPath()); 239 } 240 } 241 } 242