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