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