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 android.content.Context;
18 import android.content.res.Resources;
19 import android.hardware.display.ColorDisplayManager;
20 import android.hardware.display.NightDisplayListener;
21 import android.os.Handler;
22 import android.os.UserHandle;
23 import android.util.Log;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.systemui.R;
27 import com.android.systemui.dagger.qualifiers.Background;
28 import com.android.systemui.qs.AutoAddTracker;
29 import com.android.systemui.qs.QSTileHost;
30 import com.android.systemui.qs.SecureSetting;
31 import com.android.systemui.qs.external.CustomTile;
32 import com.android.systemui.statusbar.policy.CastController;
33 import com.android.systemui.statusbar.policy.CastController.CastDevice;
34 import com.android.systemui.statusbar.policy.DataSaverController;
35 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
36 import com.android.systemui.statusbar.policy.HotspotController;
37 import com.android.systemui.statusbar.policy.HotspotController.Callback;
38 import com.android.systemui.util.UserAwareController;
39 
40 import java.util.ArrayList;
41 import java.util.Objects;
42 
43 import javax.inject.Inject;
44 
45 /**
46  * Manages which tiles should be automatically added to QS.
47  */
48 public class AutoTileManager implements UserAwareController {
49     private static final String TAG = "AutoTileManager";
50 
51     public static final String HOTSPOT = "hotspot";
52     public static final String SAVER = "saver";
53     public static final String INVERSION = "inversion";
54     public static final String WORK = "work";
55     public static final String NIGHT = "night";
56     public static final String CAST = "cast";
57     static final String SETTING_SEPARATOR = ":";
58 
59     private UserHandle mCurrentUser;
60 
61     private final Context mContext;
62     private final QSTileHost mHost;
63     private final Handler mHandler;
64     private final AutoAddTracker mAutoTracker;
65     private final HotspotController mHotspotController;
66     private final DataSaverController mDataSaverController;
67     private final ManagedProfileController mManagedProfileController;
68     private final NightDisplayListener mNightDisplayListener;
69     private final CastController mCastController;
70     private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
71 
72     @Inject
AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, QSTileHost host, @Background Handler handler, HotspotController hotspotController, DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, CastController castController)73     public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
74             QSTileHost host,
75             @Background Handler handler,
76             HotspotController hotspotController,
77             DataSaverController dataSaverController,
78             ManagedProfileController managedProfileController,
79             NightDisplayListener nightDisplayListener,
80             CastController castController) {
81         mContext = context;
82         mHost = host;
83         mCurrentUser = mHost.getUserContext().getUser();
84         mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
85         mHandler = handler;
86         mHotspotController = hotspotController;
87         mDataSaverController = dataSaverController;
88         mManagedProfileController = managedProfileController;
89         mNightDisplayListener = nightDisplayListener;
90         mCastController = castController;
91 
92         populateSettingsList();
93         startControllersAndSettingsListeners();
94     }
95 
startControllersAndSettingsListeners()96     protected void startControllersAndSettingsListeners() {
97         if (!mAutoTracker.isAdded(HOTSPOT)) {
98             mHotspotController.addCallback(mHotspotCallback);
99         }
100         if (!mAutoTracker.isAdded(SAVER)) {
101             mDataSaverController.addCallback(mDataSaverListener);
102         }
103         if (!mAutoTracker.isAdded(WORK)) {
104             mManagedProfileController.addCallback(mProfileCallback);
105         }
106         if (!mAutoTracker.isAdded(NIGHT)
107                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
108             mNightDisplayListener.setCallback(mNightDisplayCallback);
109         }
110         if (!mAutoTracker.isAdded(CAST)) {
111             mCastController.addCallback(mCastCallback);
112         }
113 
114         int settingsN = mAutoAddSettingList.size();
115         for (int i = 0; i < settingsN; i++) {
116             if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
117                 mAutoAddSettingList.get(i).setListening(true);
118             }
119         }
120     }
121 
stopListening()122     protected void stopListening() {
123         mHotspotController.removeCallback(mHotspotCallback);
124         mDataSaverController.removeCallback(mDataSaverListener);
125         mManagedProfileController.removeCallback(mProfileCallback);
126         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
127             mNightDisplayListener.setCallback(null);
128         }
129         mCastController.removeCallback(mCastCallback);
130         int settingsN = mAutoAddSettingList.size();
131         for (int i = 0; i < settingsN; i++) {
132             mAutoAddSettingList.get(i).setListening(false);
133         }
134     }
135 
destroy()136     public void destroy() {
137         stopListening();
138         mAutoTracker.destroy();
139     }
140 
141     /**
142      * Populates a list with the pairs setting:spec in the config resource.
143      * <p>
144      * This will only create {@link AutoAddSetting} objects for those tiles that have not been
145      * auto-added before, and set the corresponding {@link ContentObserver} to listening.
146      */
populateSettingsList()147     private void populateSettingsList() {
148         String [] autoAddList;
149         try {
150             autoAddList = mContext.getResources().getStringArray(
151                     R.array.config_quickSettingsAutoAdd);
152         } catch (Resources.NotFoundException e) {
153             Log.w(TAG, "Missing config resource");
154             return;
155         }
156         // getStringArray returns @NotNull, so if we got here, autoAddList is not null
157         for (String tile : autoAddList) {
158             String[] split = tile.split(SETTING_SEPARATOR);
159             if (split.length == 2) {
160                 String setting = split[0];
161                 String spec = split[1];
162                 // Populate all the settings. As they may not have been added in other users
163                 AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
164                 mAutoAddSettingList.add(s);
165             } else {
166                 Log.w(TAG, "Malformed item in array: " + tile);
167             }
168         }
169     }
170 
171     @Override
changeUser(UserHandle newUser)172     public void changeUser(UserHandle newUser) {
173         if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
174             mHandler.post(() -> changeUser(newUser));
175             return;
176         }
177         if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
178             return;
179         }
180         stopListening();
181         mCurrentUser = newUser;
182         int settingsN = mAutoAddSettingList.size();
183         for (int i = 0; i < settingsN; i++) {
184             mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
185         }
186         mAutoTracker.changeUser(newUser);
187         startControllersAndSettingsListeners();
188     }
189 
190     @Override
getCurrentUserId()191     public int getCurrentUserId() {
192         return mCurrentUser.getIdentifier();
193     }
194 
unmarkTileAsAutoAdded(String tabSpec)195     public void unmarkTileAsAutoAdded(String tabSpec) {
196         mAutoTracker.setTileRemoved(tabSpec);
197     }
198 
199     private final ManagedProfileController.Callback mProfileCallback =
200             new ManagedProfileController.Callback() {
201                 @Override
202                 public void onManagedProfileChanged() {
203                     if (mAutoTracker.isAdded(WORK)) return;
204                     if (mManagedProfileController.hasActiveProfile()) {
205                         mHost.addTile(WORK);
206                         mAutoTracker.setTileAdded(WORK);
207                     }
208                 }
209 
210                 @Override
211                 public void onManagedProfileRemoved() {
212                 }
213             };
214 
215     private final DataSaverController.Listener mDataSaverListener = new Listener() {
216         @Override
217         public void onDataSaverChanged(boolean isDataSaving) {
218             if (mAutoTracker.isAdded(SAVER)) return;
219             if (isDataSaving) {
220                 mHost.addTile(SAVER);
221                 mAutoTracker.setTileAdded(SAVER);
222                 mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener));
223             }
224         }
225     };
226 
227     private final HotspotController.Callback mHotspotCallback = new Callback() {
228         @Override
229         public void onHotspotChanged(boolean enabled, int numDevices) {
230             if (mAutoTracker.isAdded(HOTSPOT)) return;
231             if (enabled) {
232                 mHost.addTile(HOTSPOT);
233                 mAutoTracker.setTileAdded(HOTSPOT);
234                 mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback));
235             }
236         }
237     };
238 
239     @VisibleForTesting
240     final NightDisplayListener.Callback mNightDisplayCallback =
241             new NightDisplayListener.Callback() {
242         @Override
243         public void onActivated(boolean activated) {
244             if (activated) {
245                 addNightTile();
246             }
247         }
248 
249         @Override
250         public void onAutoModeChanged(int autoMode) {
251             if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
252                     || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
253                 addNightTile();
254             }
255         }
256 
257         private void addNightTile() {
258             if (mAutoTracker.isAdded(NIGHT)) return;
259             mHost.addTile(NIGHT);
260             mAutoTracker.setTileAdded(NIGHT);
261             mHandler.post(() -> mNightDisplayListener.setCallback(null));
262         }
263     };
264 
265     @VisibleForTesting
266     final CastController.Callback mCastCallback = new CastController.Callback() {
267         @Override
268         public void onCastDevicesChanged() {
269             if (mAutoTracker.isAdded(CAST)) return;
270 
271             boolean isCasting = false;
272             for (CastDevice device : mCastController.getCastDevices()) {
273                 if (device.state == CastDevice.STATE_CONNECTED
274                         || device.state == CastDevice.STATE_CONNECTING) {
275                     isCasting = true;
276                     break;
277                 }
278             }
279 
280             if (isCasting) {
281                 mHost.addTile(CAST);
282                 mAutoTracker.setTileAdded(CAST);
283                 mHandler.post(() -> mCastController.removeCallback(mCastCallback));
284             }
285         }
286     };
287 
288     @VisibleForTesting
getSecureSettingForKey(String key)289     protected SecureSetting getSecureSettingForKey(String key) {
290         for (SecureSetting s : mAutoAddSettingList) {
291             if (Objects.equals(key, s.getKey())) {
292                 return s;
293             }
294         }
295         return null;
296     }
297 
298     /**
299      * Tracks tiles that should be auto added when a setting changes.
300      * <p>
301      * When the setting changes to a value different from 0, if the tile has not been auto added
302      * before, it will be added and the listener will be stopped.
303      */
304     private class AutoAddSetting extends SecureSetting {
305         private final String mSpec;
306 
AutoAddSetting(Context context, Handler handler, String setting, String tileSpec)307         AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) {
308             super(context, handler, setting);
309             mSpec = tileSpec;
310         }
311 
312         @Override
handleValueChanged(int value, boolean observedChange)313         protected void handleValueChanged(int value, boolean observedChange) {
314             if (mAutoTracker.isAdded(mSpec)) {
315                 // This should not be listening anymore
316                 mHandler.post(() -> setListening(false));
317                 return;
318             }
319             if (value != 0) {
320                 if (mSpec.startsWith(CustomTile.PREFIX)) {
321                     mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
322                 } else {
323                     mHost.addTile(mSpec);
324                 }
325                 mAutoTracker.setTileAdded(mSpec);
326                 mHandler.post(() -> setListening(false));
327             }
328         }
329     }
330 }
331