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.Trace; 32 import android.os.UserHandle; 33 import android.provider.Settings; 34 import android.provider.Settings.Secure; 35 import android.util.Log; 36 37 import androidx.annotation.NonNull; 38 39 import com.android.systemui.Dumpable; 40 import com.android.systemui.dagger.SysUISingleton; 41 import com.android.systemui.dagger.qualifiers.Main; 42 import com.android.systemui.dagger.qualifiers.UiBackground; 43 import com.android.systemui.dump.DumpManager; 44 import com.android.systemui.settings.UserTracker; 45 import com.android.systemui.statusbar.policy.ConfigurationController; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.concurrent.Executor; 50 51 import javax.inject.Inject; 52 53 /** 54 * Controller for tracking the current navigation bar mode. 55 */ 56 @SysUISingleton 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 private final UserTracker mUserTracker; 71 72 private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); 73 74 private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() { 75 @Override 76 public void onUserChanged(int newUser, @NonNull Context userContext) { 77 if (DEBUG) { 78 Log.d(TAG, "onUserChanged: " 79 + newUser); 80 } 81 82 updateCurrentInteractionMode(true /* notify */); 83 } 84 }; 85 86 // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for 87 // the secondary user (b/158613864), so we need to update the interaction mode here as well 88 // as a fallback if we don't receive the configuration change 89 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 90 @Override 91 public void onReceive(Context context, Intent intent) { 92 if (DEBUG) { 93 Log.d(TAG, "ACTION_OVERLAY_CHANGED"); 94 } 95 updateCurrentInteractionMode(true /* notify */); 96 } 97 }; 98 99 100 @Inject NavigationModeController(Context context, ConfigurationController configurationController, UserTracker userTracker, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DumpManager dumpManager)101 public NavigationModeController(Context context, 102 ConfigurationController configurationController, 103 UserTracker userTracker, 104 @Main Executor mainExecutor, 105 @UiBackground Executor uiBgExecutor, 106 DumpManager dumpManager) { 107 mContext = context; 108 mCurrentUserContext = context; 109 mUserTracker = userTracker; 110 mUserTracker.addCallback(mUserTrackerCallback, mainExecutor); 111 mOverlayManager = IOverlayManager.Stub.asInterface( 112 ServiceManager.getService(Context.OVERLAY_SERVICE)); 113 mUiBgExecutor = uiBgExecutor; 114 dumpManager.registerDumpable(getClass().getSimpleName(), this); 115 116 IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); 117 overlayFilter.addDataScheme("package"); 118 overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); 119 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); 120 121 configurationController.addCallback(new ConfigurationController.ConfigurationListener() { 122 @Override 123 public void onThemeChanged() { 124 if (DEBUG) { 125 Log.d(TAG, "onOverlayChanged"); 126 } 127 updateCurrentInteractionMode(true /* notify */); 128 } 129 }); 130 131 updateCurrentInteractionMode(false /* notify */); 132 } 133 updateCurrentInteractionMode(boolean notify)134 public void updateCurrentInteractionMode(boolean notify) { 135 Trace.beginSection("NMC#updateCurrentInteractionMode"); 136 mCurrentUserContext = getCurrentUserContext(); 137 int mode = getCurrentInteractionMode(mCurrentUserContext); 138 mUiBgExecutor.execute(() -> 139 Settings.Secure.putString(mCurrentUserContext.getContentResolver(), 140 Secure.NAVIGATION_MODE, String.valueOf(mode))); 141 if (DEBUG) { 142 Log.d(TAG, "updateCurrentInteractionMode: mode=" + mode); 143 dumpAssetPaths(mCurrentUserContext); 144 } 145 146 if (notify) { 147 for (int i = 0; i < mListeners.size(); i++) { 148 mListeners.get(i).onNavigationModeChanged(mode); 149 } 150 } 151 Trace.endSection(); 152 } 153 addListener(ModeChangedListener listener)154 public int addListener(ModeChangedListener listener) { 155 mListeners.add(listener); 156 return getCurrentInteractionMode(mCurrentUserContext); 157 } 158 removeListener(ModeChangedListener listener)159 public void removeListener(ModeChangedListener listener) { 160 mListeners.remove(listener); 161 } 162 getImeDrawsImeNavBar()163 public boolean getImeDrawsImeNavBar() { 164 return mCurrentUserContext.getResources().getBoolean( 165 com.android.internal.R.bool.config_imeDrawsImeNavBar); 166 } 167 getCurrentInteractionMode(Context context)168 private int getCurrentInteractionMode(Context context) { 169 int mode = context.getResources().getInteger( 170 com.android.internal.R.integer.config_navBarInteractionMode); 171 if (DEBUG) { 172 Log.d(TAG, "getCurrentInteractionMode: mode=" + mode 173 + " contextUser=" + context.getUserId()); 174 } 175 return mode; 176 } 177 getCurrentUserContext()178 public Context getCurrentUserContext() { 179 int userId = mUserTracker.getUserId(); 180 if (DEBUG) { 181 Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId() 182 + " currentUser=" + userId); 183 } 184 if (mContext.getUserId() == userId) { 185 return mContext; 186 } 187 try { 188 return mContext.createPackageContextAsUser(mContext.getPackageName(), 189 0 /* flags */, UserHandle.of(userId)); 190 } catch (PackageManager.NameNotFoundException e) { 191 // Never happens for the sysui package 192 Log.e(TAG, "Failed to create package context", e); 193 return null; 194 } 195 } 196 197 @Override dump(PrintWriter pw, String[] args)198 public void dump(PrintWriter pw, String[] args) { 199 pw.println("NavigationModeController:"); 200 pw.println(" mode=" + getCurrentInteractionMode(mCurrentUserContext)); 201 String defaultOverlays = ""; 202 try { 203 defaultOverlays = String.join(", ", mOverlayManager.getDefaultOverlayPackages()); 204 } catch (RemoteException e) { 205 defaultOverlays = "failed_to_fetch"; 206 } 207 pw.println(" defaultOverlays=" + defaultOverlays); 208 dumpAssetPaths(mCurrentUserContext); 209 } 210 dumpAssetPaths(Context context)211 private void dumpAssetPaths(Context context) { 212 Log.d(TAG, " contextUser=" + mCurrentUserContext.getUserId()); 213 Log.d(TAG, " assetPaths="); 214 ApkAssets[] assets = context.getResources().getAssets().getApkAssets(); 215 for (ApkAssets a : assets) { 216 Log.d(TAG, " " + a.getDebugName()); 217 } 218 } 219 } 220