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.shared.system.ActivityManagerWrapper; 40 import com.android.systemui.statusbar.policy.ConfigurationController; 41 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 42 43 import java.io.FileDescriptor; 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)99 public NavigationModeController(Context context, 100 DeviceProvisionedController deviceProvisionedController, 101 ConfigurationController configurationController, 102 @UiBackground Executor uiBgExecutor) { 103 mContext = context; 104 mCurrentUserContext = context; 105 mOverlayManager = IOverlayManager.Stub.asInterface( 106 ServiceManager.getService(Context.OVERLAY_SERVICE)); 107 mUiBgExecutor = uiBgExecutor; 108 deviceProvisionedController.addCallback(mDeviceProvisionedCallback); 109 110 IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); 111 overlayFilter.addDataScheme("package"); 112 overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); 113 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); 114 115 configurationController.addCallback(new ConfigurationController.ConfigurationListener() { 116 @Override 117 public void onOverlayChanged() { 118 if (DEBUG) { 119 Log.d(TAG, "onOverlayChanged"); 120 } 121 updateCurrentInteractionMode(true /* notify */); 122 } 123 }); 124 125 updateCurrentInteractionMode(false /* notify */); 126 } 127 updateCurrentInteractionMode(boolean notify)128 public void updateCurrentInteractionMode(boolean notify) { 129 mCurrentUserContext = getCurrentUserContext(); 130 int mode = getCurrentInteractionMode(mCurrentUserContext); 131 mUiBgExecutor.execute(() -> 132 Settings.Secure.putString(mCurrentUserContext.getContentResolver(), 133 Secure.NAVIGATION_MODE, String.valueOf(mode))); 134 if (DEBUG) { 135 Log.d(TAG, "updateCurrentInteractionMode: mode=" + mode); 136 dumpAssetPaths(mCurrentUserContext); 137 } 138 139 if (notify) { 140 for (int i = 0; i < mListeners.size(); i++) { 141 mListeners.get(i).onNavigationModeChanged(mode); 142 } 143 } 144 } 145 addListener(ModeChangedListener listener)146 public int addListener(ModeChangedListener listener) { 147 mListeners.add(listener); 148 return getCurrentInteractionMode(mCurrentUserContext); 149 } 150 removeListener(ModeChangedListener listener)151 public void removeListener(ModeChangedListener listener) { 152 mListeners.remove(listener); 153 } 154 getCurrentInteractionMode(Context context)155 private int getCurrentInteractionMode(Context context) { 156 int mode = context.getResources().getInteger( 157 com.android.internal.R.integer.config_navBarInteractionMode); 158 if (DEBUG) { 159 Log.d(TAG, "getCurrentInteractionMode: mode=" + mode 160 + " contextUser=" + context.getUserId()); 161 } 162 return mode; 163 } 164 getCurrentUserContext()165 public Context getCurrentUserContext() { 166 int userId = ActivityManagerWrapper.getInstance().getCurrentUserId(); 167 if (DEBUG) { 168 Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId() 169 + " currentUser=" + userId); 170 } 171 if (mContext.getUserId() == userId) { 172 return mContext; 173 } 174 try { 175 return mContext.createPackageContextAsUser(mContext.getPackageName(), 176 0 /* flags */, UserHandle.of(userId)); 177 } catch (PackageManager.NameNotFoundException e) { 178 // Never happens for the sysui package 179 Log.e(TAG, "Failed to create package context", e); 180 return null; 181 } 182 } 183 184 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)185 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 186 pw.println("NavigationModeController:"); 187 pw.println(" mode=" + getCurrentInteractionMode(mCurrentUserContext)); 188 String defaultOverlays = ""; 189 try { 190 defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages()); 191 } catch (RemoteException e) { 192 defaultOverlays = "failed_to_fetch"; 193 } 194 pw.println(" defaultOverlays=" + defaultOverlays); 195 dumpAssetPaths(mCurrentUserContext); 196 } 197 dumpAssetPaths(Context context)198 private void dumpAssetPaths(Context context) { 199 Log.d(TAG, " contextUser=" + mCurrentUserContext.getUserId()); 200 Log.d(TAG, " assetPaths="); 201 ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); 202 for (ApkAssets a : assets) { 203 Log.d(TAG, " " + a.getDebugName()); 204 } 205 } 206 } 207