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.navigationbar; 18 19 import static android.content.Intent.ACTION_OVERLAY_CHANGED; 20 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.om.IOverlayManager; 26 import android.content.pm.PackageManager; 27 import android.content.res.ApkAssets; 28 import android.os.PatternMatcher; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.os.UserHandle; 32 import android.provider.Settings; 33 import android.provider.Settings.Secure; 34 import android.util.Log; 35 36 import com.android.systemui.Dumpable; 37 import com.android.systemui.dagger.SysUISingleton; 38 import com.android.systemui.dagger.qualifiers.UiBackground; 39 import com.android.systemui.dump.DumpManager; 40 import com.android.systemui.shared.system.ActivityManagerWrapper; 41 import com.android.systemui.statusbar.policy.ConfigurationController; 42 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 43 44 import java.io.PrintWriter; 45 import java.util.ArrayList; 46 import java.util.concurrent.Executor; 47 48 import javax.inject.Inject; 49 50 /** 51 * Controller for tracking the current navigation bar mode. 52 */ 53 @SysUISingleton 54 public class NavigationModeController implements Dumpable { 55 56 private static final String TAG = NavigationModeController.class.getSimpleName(); 57 private static final boolean DEBUG = true; 58 59 public interface ModeChangedListener { onNavigationModeChanged(int mode)60 void onNavigationModeChanged(int mode); 61 } 62 63 private final Context mContext; 64 private Context mCurrentUserContext; 65 private final IOverlayManager mOverlayManager; 66 private final Executor mUiBgExecutor; 67 68 private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); 69 70 private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback = 71 new DeviceProvisionedController.DeviceProvisionedListener() { 72 @Override 73 public void onUserSwitched() { 74 if (DEBUG) { 75 Log.d(TAG, "onUserSwitched: " 76 + ActivityManagerWrapper.getInstance().getCurrentUserId()); 77 } 78 79 // Update the nav mode for the current user 80 updateCurrentInteractionMode(true /* notify */); 81 } 82 }; 83 84 // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for 85 // the secondary user (b/158613864), so we need to update the interaction mode here as well 86 // as a fallback if we don't receive the configuration change 87 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 88 @Override 89 public void onReceive(Context context, Intent intent) { 90 if (DEBUG) { 91 Log.d(TAG, "ACTION_OVERLAY_CHANGED"); 92 } 93 updateCurrentInteractionMode(true /* notify */); 94 } 95 }; 96 97 98 @Inject NavigationModeController(Context context, DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, @UiBackground Executor uiBgExecutor, DumpManager dumpManager)99 public NavigationModeController(Context context, 100 DeviceProvisionedController deviceProvisionedController, 101 ConfigurationController configurationController, 102 @UiBackground Executor uiBgExecutor, 103 DumpManager dumpManager) { 104 mContext = context; 105 mCurrentUserContext = context; 106 mOverlayManager = IOverlayManager.Stub.asInterface( 107 ServiceManager.getService(Context.OVERLAY_SERVICE)); 108 mUiBgExecutor = uiBgExecutor; 109 dumpManager.registerDumpable(getClass().getSimpleName(), this); 110 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 onThemeChanged() { 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 mUiBgExecutor.execute(() -> 135 Settings.Secure.putString(mCurrentUserContext.getContentResolver(), 136 Secure.NAVIGATION_MODE, String.valueOf(mode))); 137 if (DEBUG) { 138 Log.d(TAG, "updateCurrentInteractionMode: mode=" + mode); 139 dumpAssetPaths(mCurrentUserContext); 140 } 141 142 if (notify) { 143 for (int i = 0; i < mListeners.size(); i++) { 144 mListeners.get(i).onNavigationModeChanged(mode); 145 } 146 } 147 } 148 addListener(ModeChangedListener listener)149 public int addListener(ModeChangedListener listener) { 150 mListeners.add(listener); 151 return getCurrentInteractionMode(mCurrentUserContext); 152 } 153 removeListener(ModeChangedListener listener)154 public void removeListener(ModeChangedListener listener) { 155 mListeners.remove(listener); 156 } 157 getImeDrawsImeNavBar()158 public boolean getImeDrawsImeNavBar() { 159 return mCurrentUserContext.getResources().getBoolean( 160 com.android.internal.R.bool.config_imeDrawsImeNavBar); 161 } 162 getCurrentInteractionMode(Context context)163 private int getCurrentInteractionMode(Context context) { 164 int mode = context.getResources().getInteger( 165 com.android.internal.R.integer.config_navBarInteractionMode); 166 if (DEBUG) { 167 Log.d(TAG, "getCurrentInteractionMode: mode=" + mode 168 + " contextUser=" + context.getUserId()); 169 } 170 return mode; 171 } 172 getCurrentUserContext()173 public Context getCurrentUserContext() { 174 int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); 175 if (DEBUG) { 176 Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId() 177 + " currentUser=" + userId); 178 } 179 if (mContext.getUserId() == userId) { 180 return mContext; 181 } 182 try { 183 return mContext.createPackageContextAsUser(mContext.getPackageName(), 184 0 /* flags */, UserHandle.of(userId)); 185 } catch (PackageManager.NameNotFoundException e) { 186 // Never happens for the sysui package 187 Log.e(TAG, "Failed to create package context", e); 188 return null; 189 } 190 } 191 192 @Override dump(PrintWriter pw, String[] args)193 public void dump(PrintWriter pw, String[] args) { 194 pw.println("NavigationModeController:"); 195 pw.println(" mode=" + getCurrentInteractionMode(mCurrentUserContext)); 196 String defaultOverlays = ""; 197 try { 198 defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages()); 199 } catch (RemoteException e) { 200 defaultOverlays = "failed_to_fetch"; 201 } 202 pw.println(" defaultOverlays=" + defaultOverlays); 203 dumpAssetPaths(mCurrentUserContext); 204 } 205 dumpAssetPaths(Context context)206 private void dumpAssetPaths(Context context) { 207 Log.d(TAG, " contextUser=" + mCurrentUserContext.getUserId()); 208 Log.d(TAG, " assetPaths="); 209 ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); 210 for (ApkAssets a : assets) { 211 Log.d(TAG, " " + a.getDebugName()); 212 } 213 } 214 } 215