• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.phone;
16 
17 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
18 
19 import android.annotation.Nullable;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.hardware.display.ColorDisplayManager;
24 import android.hardware.display.NightDisplayListener;
25 import android.os.Handler;
26 import android.os.UserHandle;
27 import android.util.Log;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.systemui.R;
31 import com.android.systemui.dagger.qualifiers.Background;
32 import com.android.systemui.plugins.qs.QSTile;
33 import com.android.systemui.qs.AutoAddTracker;
34 import com.android.systemui.qs.QSHost;
35 import com.android.systemui.qs.ReduceBrightColorsController;
36 import com.android.systemui.qs.SettingObserver;
37 import com.android.systemui.qs.external.CustomTile;
38 import com.android.systemui.statusbar.policy.CastController;
39 import com.android.systemui.statusbar.policy.CastController.CastDevice;
40 import com.android.systemui.statusbar.policy.DataSaverController;
41 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
42 import com.android.systemui.statusbar.policy.DeviceControlsController;
43 import com.android.systemui.statusbar.policy.HotspotController;
44 import com.android.systemui.statusbar.policy.HotspotController.Callback;
45 import com.android.systemui.statusbar.policy.SafetyController;
46 import com.android.systemui.statusbar.policy.WalletController;
47 import com.android.systemui.util.UserAwareController;
48 import com.android.systemui.util.settings.SecureSettings;
49 
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Objects;
53 
54 import javax.inject.Named;
55 
56 /**
57  * Manages which tiles should be automatically added to QS.
58  */
59 public class AutoTileManager implements UserAwareController {
60     private static final String TAG = "AutoTileManager";
61 
62     public static final String HOTSPOT = "hotspot";
63     public static final String SAVER = "saver";
64     public static final String INVERSION = "inversion";
65     public static final String WORK = "work";
66     public static final String NIGHT = "night";
67     public static final String CAST = "cast";
68     public static final String DEVICE_CONTROLS = "controls";
69     public static final String WALLET = "wallet";
70     public static final String BRIGHTNESS = "reduce_brightness";
71     static final String SETTING_SEPARATOR = ":";
72 
73     private UserHandle mCurrentUser;
74     private boolean mInitialized;
75     private final String mSafetySpec;
76 
77     protected final Context mContext;
78     protected final QSHost mHost;
79     protected final Handler mHandler;
80     protected final SecureSettings mSecureSettings;
81     protected final AutoAddTracker mAutoTracker;
82     private final HotspotController mHotspotController;
83     private final DataSaverController mDataSaverController;
84     private final ManagedProfileController mManagedProfileController;
85     private final NightDisplayListener mNightDisplayListener;
86     private final CastController mCastController;
87     private final DeviceControlsController mDeviceControlsController;
88     private final WalletController mWalletController;
89     private final ReduceBrightColorsController mReduceBrightColorsController;
90     private final SafetyController mSafetyController;
91     private final boolean mIsReduceBrightColorsAvailable;
92     private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
93 
AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, QSHost host, @Background Handler handler, SecureSettings secureSettings, HotspotController hotspotController, DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, CastController castController, ReduceBrightColorsController reduceBrightColorsController, DeviceControlsController deviceControlsController, WalletController walletController, SafetyController safetyController, @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable)94     public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
95             QSHost host,
96             @Background Handler handler,
97             SecureSettings secureSettings,
98             HotspotController hotspotController,
99             DataSaverController dataSaverController,
100             ManagedProfileController managedProfileController,
101             NightDisplayListener nightDisplayListener,
102             CastController castController,
103             ReduceBrightColorsController reduceBrightColorsController,
104             DeviceControlsController deviceControlsController,
105             WalletController walletController,
106             SafetyController safetyController,
107             @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
108         mContext = context;
109         mHost = host;
110         mSecureSettings = secureSettings;
111         mCurrentUser = mHost.getUserContext().getUser();
112         mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
113         mHandler = handler;
114         mHotspotController = hotspotController;
115         mDataSaverController = dataSaverController;
116         mManagedProfileController = managedProfileController;
117         mNightDisplayListener = nightDisplayListener;
118         mCastController = castController;
119         mReduceBrightColorsController = reduceBrightColorsController;
120         mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
121         mDeviceControlsController = deviceControlsController;
122         mWalletController = walletController;
123         mSafetyController = safetyController;
124         String safetySpecClass;
125         try {
126             safetySpecClass =
127                     context.getResources().getString(R.string.safety_quick_settings_tile_class);
128             if (safetySpecClass.length() == 0) {
129                 safetySpecClass = null;
130             }
131         } catch (Resources.NotFoundException | NullPointerException e) {
132             safetySpecClass = null;
133         }
134         mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext
135                 .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null;
136     }
137 
138     /**
139      * Init method must be called after construction to start listening
140      */
init()141     public void init() {
142         if (mInitialized) {
143             Log.w(TAG, "Trying to re-initialize");
144             return;
145         }
146         mAutoTracker.initialize();
147         populateSettingsList();
148         startControllersAndSettingsListeners();
149         mInitialized = true;
150     }
151 
startControllersAndSettingsListeners()152     protected void startControllersAndSettingsListeners() {
153         if (!mAutoTracker.isAdded(HOTSPOT)) {
154             mHotspotController.addCallback(mHotspotCallback);
155         }
156         if (!mAutoTracker.isAdded(SAVER)) {
157             mDataSaverController.addCallback(mDataSaverListener);
158         }
159         mManagedProfileController.addCallback(mProfileCallback);
160         if (!mAutoTracker.isAdded(NIGHT)
161                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
162             mNightDisplayListener.setCallback(mNightDisplayCallback);
163         }
164         if (!mAutoTracker.isAdded(CAST)) {
165             mCastController.addCallback(mCastCallback);
166         }
167         if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
168             mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
169         }
170         // We always want this callback, because if the feature stops being supported,
171         // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
172         // feature is reenabled (similar to work tile).
173         mDeviceControlsController.setCallback(mDeviceControlsCallback);
174         if (!mAutoTracker.isAdded(WALLET)) {
175             initWalletController();
176         }
177         if (mSafetySpec != null) {
178             if (!mAutoTracker.isAdded(mSafetySpec)) {
179                 initSafetyTile();
180             }
181             mSafetyController.addCallback(mSafetyCallback);
182         }
183 
184         int settingsN = mAutoAddSettingList.size();
185         for (int i = 0; i < settingsN; i++) {
186             if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
187                 mAutoAddSettingList.get(i).setListening(true);
188             }
189         }
190     }
191 
stopListening()192     protected void stopListening() {
193         mHotspotController.removeCallback(mHotspotCallback);
194         mDataSaverController.removeCallback(mDataSaverListener);
195         mManagedProfileController.removeCallback(mProfileCallback);
196         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
197             mNightDisplayListener.setCallback(null);
198         }
199         if (mIsReduceBrightColorsAvailable) {
200             mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
201         }
202         mCastController.removeCallback(mCastCallback);
203         mDeviceControlsController.removeCallback();
204         if (mSafetySpec != null) {
205             mSafetyController.removeCallback(mSafetyCallback);
206         }
207         int settingsN = mAutoAddSettingList.size();
208         for (int i = 0; i < settingsN; i++) {
209             mAutoAddSettingList.get(i).setListening(false);
210         }
211     }
212 
destroy()213     public void destroy() {
214         stopListening();
215         mAutoTracker.destroy();
216     }
217 
218     /**
219      * Populates a list with the pairs setting:spec in the config resource.
220      * <p>
221      * This will only create {@link AutoAddSetting} objects for those tiles that have not been
222      * auto-added before, and set the corresponding {@link ContentObserver} to listening.
223      */
populateSettingsList()224     private void populateSettingsList() {
225         String [] autoAddList;
226         try {
227             autoAddList = mContext.getResources().getStringArray(
228                     R.array.config_quickSettingsAutoAdd);
229         } catch (Resources.NotFoundException e) {
230             Log.w(TAG, "Missing config resource");
231             return;
232         }
233         // getStringArray returns @NotNull, so if we got here, autoAddList is not null
234         for (String tile : autoAddList) {
235             String[] split = tile.split(SETTING_SEPARATOR);
236             if (split.length == 2) {
237                 String setting = split[0];
238                 String spec = split[1];
239                 // Populate all the settings. As they may not have been added in other users
240                 AutoAddSetting s = new AutoAddSetting(
241                         mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec);
242                 mAutoAddSettingList.add(s);
243             } else {
244                 Log.w(TAG, "Malformed item in array: " + tile);
245             }
246         }
247     }
248 
249     /*
250      * This will be sent off the main thread if needed
251      */
252     @Override
changeUser(UserHandle newUser)253     public void changeUser(UserHandle newUser) {
254         if (!mInitialized) {
255             throw new IllegalStateException("AutoTileManager not initialized");
256         }
257         if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
258             mHandler.post(() -> changeUser(newUser));
259             return;
260         }
261         if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
262             return;
263         }
264         stopListening();
265         mCurrentUser = newUser;
266         int settingsN = mAutoAddSettingList.size();
267         for (int i = 0; i < settingsN; i++) {
268             mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
269         }
270         mAutoTracker.changeUser(newUser);
271         startControllersAndSettingsListeners();
272     }
273 
274     @Override
getCurrentUserId()275     public int getCurrentUserId() {
276         return mCurrentUser.getIdentifier();
277     }
278 
279     private final ManagedProfileController.Callback mProfileCallback =
280             new ManagedProfileController.Callback() {
281                 @Override
282                 public void onManagedProfileChanged() {
283                     if (mManagedProfileController.hasActiveProfile()) {
284                         if (mAutoTracker.isAdded(WORK)) return;
285                         final int position = mAutoTracker.getRestoredTilePosition(WORK);
286                         mHost.addTile(WORK, position);
287                         mAutoTracker.setTileAdded(WORK);
288                     } else {
289                         if (!mAutoTracker.isAdded(WORK)) return;
290                         mHost.removeTile(WORK);
291                         mAutoTracker.setTileRemoved(WORK);
292                     }
293                 }
294 
295                 @Override
296                 public void onManagedProfileRemoved() {
297                 }
298             };
299 
300     private final DataSaverController.Listener mDataSaverListener = new Listener() {
301         @Override
302         public void onDataSaverChanged(boolean isDataSaving) {
303             if (mAutoTracker.isAdded(SAVER)) return;
304             if (isDataSaving) {
305                 mHost.addTile(SAVER);
306                 mAutoTracker.setTileAdded(SAVER);
307                 mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener));
308             }
309         }
310     };
311 
312     private final HotspotController.Callback mHotspotCallback = new Callback() {
313         @Override
314         public void onHotspotChanged(boolean enabled, int numDevices) {
315             if (mAutoTracker.isAdded(HOTSPOT)) return;
316             if (enabled) {
317                 mHost.addTile(HOTSPOT);
318                 mAutoTracker.setTileAdded(HOTSPOT);
319                 mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback));
320             }
321         }
322     };
323 
324     private final DeviceControlsController.Callback mDeviceControlsCallback =
325             new DeviceControlsController.Callback() {
326         @Override
327         public void onControlsUpdate(@Nullable Integer position) {
328             if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
329             if (position != null && !hasTile(DEVICE_CONTROLS)) {
330                 mHost.addTile(DEVICE_CONTROLS, position);
331                 mAutoTracker.setTileAdded(DEVICE_CONTROLS);
332             }
333             mHandler.post(() -> mDeviceControlsController.removeCallback());
334         }
335 
336         @Override
337         public void removeControlsAutoTracker() {
338             mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
339         }
340     };
341 
hasTile(String tileSpec)342     private boolean hasTile(String tileSpec) {
343         if (tileSpec == null) return false;
344         Collection<QSTile> tiles = mHost.getTiles();
345         for (QSTile tile : tiles) {
346             if (tileSpec.equals(tile.getTileSpec())) {
347                 return true;
348             }
349         }
350         return false;
351     }
352 
initWalletController()353     private void initWalletController() {
354         if (mAutoTracker.isAdded(WALLET)) return;
355         Integer position = mWalletController.getWalletPosition();
356 
357         if (position != null) {
358             mHost.addTile(WALLET, position);
359             mAutoTracker.setTileAdded(WALLET);
360         }
361     }
362 
initSafetyTile()363     private void initSafetyTile() {
364         if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
365             return;
366         }
367         mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
368         mAutoTracker.setTileAdded(mSafetySpec);
369     }
370 
371     @VisibleForTesting
372     final NightDisplayListener.Callback mNightDisplayCallback =
373             new NightDisplayListener.Callback() {
374         @Override
375         public void onActivated(boolean activated) {
376             if (activated) {
377                 addNightTile();
378             }
379         }
380 
381         @Override
382         public void onAutoModeChanged(int autoMode) {
383             if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
384                     || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
385                 addNightTile();
386             }
387         }
388 
389         private void addNightTile() {
390             if (mAutoTracker.isAdded(NIGHT)) return;
391             mHost.addTile(NIGHT);
392             mAutoTracker.setTileAdded(NIGHT);
393             mHandler.post(() -> mNightDisplayListener.setCallback(null));
394         }
395     };
396 
397     @VisibleForTesting
398     final ReduceBrightColorsController.Listener mReduceBrightColorsCallback =
399             new ReduceBrightColorsController.Listener() {
400                 @Override
401                 public void onActivated(boolean activated) {
402                     if (activated) {
403                         addReduceBrightColorsTile();
404                     }
405                 }
406 
407                 private void addReduceBrightColorsTile() {
408                     if (mAutoTracker.isAdded(BRIGHTNESS)) return;
409                     mHost.addTile(BRIGHTNESS);
410                     mAutoTracker.setTileAdded(BRIGHTNESS);
411                     mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
412                 }
413             };
414 
415     @VisibleForTesting
416     final CastController.Callback mCastCallback = new CastController.Callback() {
417         @Override
418         public void onCastDevicesChanged() {
419             if (mAutoTracker.isAdded(CAST)) return;
420 
421             boolean isCasting = false;
422             for (CastDevice device : mCastController.getCastDevices()) {
423                 if (device.state == CastDevice.STATE_CONNECTED
424                         || device.state == CastDevice.STATE_CONNECTING) {
425                     isCasting = true;
426                     break;
427                 }
428             }
429 
430             if (isCasting) {
431                 mHost.addTile(CAST);
432                 mAutoTracker.setTileAdded(CAST);
433                 mHandler.post(() -> mCastController.removeCallback(mCastCallback));
434             }
435         }
436     };
437 
438     @VisibleForTesting
439     final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() {
440         @Override
441         public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) {
442             if (mSafetySpec == null) {
443                 return;
444             }
445 
446             if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
447                 initSafetyTile();
448             } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
449                 mHost.removeTile(mSafetySpec);
450                 mAutoTracker.setTileRemoved(mSafetySpec);
451             }
452         }
453     };
454 
455     @VisibleForTesting
getSecureSettingForKey(String key)456     protected SettingObserver getSecureSettingForKey(String key) {
457         for (SettingObserver s : mAutoAddSettingList) {
458             if (Objects.equals(key, s.getKey())) {
459                 return s;
460             }
461         }
462         return null;
463     }
464 
465     /**
466      * Tracks tiles that should be auto added when a setting changes.
467      * <p>
468      * When the setting changes to a value different from 0, if the tile has not been auto added
469      * before, it will be added and the listener will be stopped.
470      */
471     private class AutoAddSetting extends SettingObserver {
472         private final String mSpec;
473 
AutoAddSetting( SecureSettings secureSettings, Handler handler, String setting, int userId, String tileSpec )474         AutoAddSetting(
475                 SecureSettings secureSettings,
476                 Handler handler,
477                 String setting,
478                 int userId,
479                 String tileSpec
480         ) {
481             super(secureSettings, handler, setting, userId);
482             mSpec = tileSpec;
483         }
484 
485         @Override
handleValueChanged(int value, boolean observedChange)486         protected void handleValueChanged(int value, boolean observedChange) {
487             if (mAutoTracker.isAdded(mSpec)) {
488                 // This should not be listening anymore
489                 mHandler.post(() -> setListening(false));
490                 return;
491             }
492             if (value != 0) {
493                 if (mSpec.startsWith(CustomTile.PREFIX)) {
494                     mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
495                 } else {
496                     mHost.addTile(mSpec);
497                 }
498                 mAutoTracker.setTileAdded(mSpec);
499                 mHandler.post(() -> setListening(false));
500             }
501         }
502     }
503 }
504