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