1/*
2 * Copyright (C) 2014 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
17package com.android.systemui.statusbar.phone;
18
19import android.app.ActivityManager;
20import android.app.PendingIntent;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.os.Looper;
28import android.os.Process;
29import android.os.UserHandle;
30import android.os.UserManager;
31import android.provider.Settings;
32import android.provider.Settings.Secure;
33import android.service.quicksettings.Tile;
34import android.text.TextUtils;
35import android.util.Log;
36import android.view.View;
37
38import com.android.systemui.R;
39import com.android.systemui.qs.QSTile;
40import com.android.systemui.qs.external.CustomTile;
41import com.android.systemui.qs.external.TileLifecycleManager;
42import com.android.systemui.qs.external.TileServices;
43import com.android.systemui.qs.tiles.AirplaneModeTile;
44import com.android.systemui.qs.tiles.BatteryTile;
45import com.android.systemui.qs.tiles.BluetoothTile;
46import com.android.systemui.qs.tiles.CastTile;
47import com.android.systemui.qs.tiles.CellularTile;
48import com.android.systemui.qs.tiles.ColorInversionTile;
49import com.android.systemui.qs.tiles.DataSaverTile;
50import com.android.systemui.qs.tiles.DndTile;
51import com.android.systemui.qs.tiles.FlashlightTile;
52import com.android.systemui.qs.tiles.HotspotTile;
53import com.android.systemui.qs.tiles.IntentTile;
54import com.android.systemui.qs.tiles.LocationTile;
55import com.android.systemui.qs.tiles.NightDisplayTile;
56import com.android.systemui.qs.tiles.RotationLockTile;
57import com.android.systemui.qs.tiles.UserTile;
58import com.android.systemui.qs.tiles.WifiTile;
59import com.android.systemui.qs.tiles.WorkModeTile;
60import com.android.systemui.statusbar.policy.BatteryController;
61import com.android.systemui.statusbar.policy.BluetoothController;
62import com.android.systemui.statusbar.policy.CastController;
63import com.android.systemui.statusbar.policy.NextAlarmController;
64import com.android.systemui.statusbar.policy.FlashlightController;
65import com.android.systemui.statusbar.policy.HotspotController;
66import com.android.systemui.statusbar.policy.KeyguardMonitor;
67import com.android.systemui.statusbar.policy.LocationController;
68import com.android.systemui.statusbar.policy.NetworkController;
69import com.android.systemui.statusbar.policy.RotationLockController;
70import com.android.systemui.statusbar.policy.SecurityController;
71import com.android.systemui.statusbar.policy.UserInfoController;
72import com.android.systemui.statusbar.policy.UserSwitcherController;
73import com.android.systemui.statusbar.policy.ZenModeController;
74import com.android.systemui.tuner.TunerService;
75import com.android.systemui.tuner.TunerService.Tunable;
76
77import java.util.ArrayList;
78import java.util.Arrays;
79import java.util.Collection;
80import java.util.LinkedHashMap;
81import java.util.List;
82import java.util.Map;
83
84/** Platform implementation of the quick settings tile host **/
85public class QSTileHost implements QSTile.Host, Tunable {
86    private static final String TAG = "QSTileHost";
87    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
88
89    public static final String TILES_SETTING = Secure.QS_TILES;
90
91    private final Context mContext;
92    private final PhoneStatusBar mStatusBar;
93    private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
94    protected final ArrayList<String> mTileSpecs = new ArrayList<>();
95    private final BluetoothController mBluetooth;
96    private final LocationController mLocation;
97    private final RotationLockController mRotation;
98    private final NetworkController mNetwork;
99    private final ZenModeController mZen;
100    private final HotspotController mHotspot;
101    private final CastController mCast;
102    private final Looper mLooper;
103    private final FlashlightController mFlashlight;
104    private final UserSwitcherController mUserSwitcherController;
105    private final UserInfoController mUserInfoController;
106    private final KeyguardMonitor mKeyguard;
107    private final SecurityController mSecurity;
108    private final BatteryController mBattery;
109    private final StatusBarIconController mIconController;
110    private final TileServices mServices;
111
112    private final List<Callback> mCallbacks = new ArrayList<>();
113    private final AutoTileManager mAutoTiles;
114    private final ManagedProfileController mProfileController;
115    private final NextAlarmController mNextAlarmController;
116    private View mHeader;
117    private int mCurrentUser;
118
119    public QSTileHost(Context context, PhoneStatusBar statusBar,
120            BluetoothController bluetooth, LocationController location,
121            RotationLockController rotation, NetworkController network,
122            ZenModeController zen, HotspotController hotspot,
123            CastController cast, FlashlightController flashlight,
124            UserSwitcherController userSwitcher, UserInfoController userInfo,
125            KeyguardMonitor keyguard, SecurityController security,
126            BatteryController battery, StatusBarIconController iconController,
127            NextAlarmController nextAlarmController) {
128        mContext = context;
129        mStatusBar = statusBar;
130        mBluetooth = bluetooth;
131        mLocation = location;
132        mRotation = rotation;
133        mNetwork = network;
134        mZen = zen;
135        mHotspot = hotspot;
136        mCast = cast;
137        mFlashlight = flashlight;
138        mUserSwitcherController = userSwitcher;
139        mUserInfoController = userInfo;
140        mKeyguard = keyguard;
141        mSecurity = security;
142        mBattery = battery;
143        mIconController = iconController;
144        mNextAlarmController = nextAlarmController;
145        mProfileController = new ManagedProfileController(this);
146
147        final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
148                Process.THREAD_PRIORITY_BACKGROUND);
149        ht.start();
150        mLooper = ht.getLooper();
151
152        mServices = new TileServices(this, mLooper);
153
154        TunerService.get(mContext).addTunable(this, TILES_SETTING);
155        // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
156        mAutoTiles = new AutoTileManager(context, this);
157    }
158
159    public NextAlarmController getNextAlarmController() {
160        return mNextAlarmController;
161    }
162
163    public void setHeaderView(View view) {
164        mHeader = view;
165    }
166
167    public PhoneStatusBar getPhoneStatusBar() {
168        return mStatusBar;
169    }
170
171    public void destroy() {
172        mAutoTiles.destroy();
173        TunerService.get(mContext).removeTunable(this);
174    }
175
176    @Override
177    public void addCallback(Callback callback) {
178        mCallbacks.add(callback);
179    }
180
181    @Override
182    public void removeCallback(Callback callback) {
183        mCallbacks.remove(callback);
184    }
185
186    @Override
187    public Collection<QSTile<?>> getTiles() {
188        return mTiles.values();
189    }
190
191    @Override
192    public void startActivityDismissingKeyguard(final Intent intent) {
193        mStatusBar.postStartActivityDismissingKeyguard(intent, 0);
194    }
195
196    @Override
197    public void startActivityDismissingKeyguard(PendingIntent intent) {
198        mStatusBar.postStartActivityDismissingKeyguard(intent);
199    }
200
201    @Override
202    public void startRunnableDismissingKeyguard(Runnable runnable) {
203        mStatusBar.postQSRunnableDismissingKeyguard(runnable);
204    }
205
206    @Override
207    public void warn(String message, Throwable t) {
208        // already logged
209    }
210
211    public void animateToggleQSExpansion() {
212        // TODO: Better path to animated panel expansion.
213        mHeader.callOnClick();
214    }
215
216    @Override
217    public void collapsePanels() {
218        mStatusBar.postAnimateCollapsePanels();
219    }
220
221    @Override
222    public void openPanels() {
223        mStatusBar.postAnimateOpenPanels();
224    }
225
226    @Override
227    public Looper getLooper() {
228        return mLooper;
229    }
230
231    @Override
232    public Context getContext() {
233        return mContext;
234    }
235
236    @Override
237    public BluetoothController getBluetoothController() {
238        return mBluetooth;
239    }
240
241    @Override
242    public LocationController getLocationController() {
243        return mLocation;
244    }
245
246    @Override
247    public RotationLockController getRotationLockController() {
248        return mRotation;
249    }
250
251    @Override
252    public NetworkController getNetworkController() {
253        return mNetwork;
254    }
255
256    @Override
257    public ZenModeController getZenModeController() {
258        return mZen;
259    }
260
261    @Override
262    public HotspotController getHotspotController() {
263        return mHotspot;
264    }
265
266    @Override
267    public CastController getCastController() {
268        return mCast;
269    }
270
271    @Override
272    public FlashlightController getFlashlightController() {
273        return mFlashlight;
274    }
275
276    @Override
277    public KeyguardMonitor getKeyguardMonitor() {
278        return mKeyguard;
279    }
280
281    @Override
282    public UserSwitcherController getUserSwitcherController() {
283        return mUserSwitcherController;
284    }
285
286    @Override
287    public UserInfoController getUserInfoController() {
288        return mUserInfoController;
289    }
290
291    @Override
292    public BatteryController getBatteryController() {
293        return mBattery;
294    }
295
296    public SecurityController getSecurityController() {
297        return mSecurity;
298    }
299
300    public TileServices getTileServices() {
301        return mServices;
302    }
303
304    public StatusBarIconController getIconController() {
305        return mIconController;
306    }
307
308    public ManagedProfileController getManagedProfileController() {
309        return mProfileController;
310    }
311
312    @Override
313    public void onTuningChanged(String key, String newValue) {
314        if (!TILES_SETTING.equals(key)) {
315            return;
316        }
317        if (DEBUG) Log.d(TAG, "Recreating tiles");
318        if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
319            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
320        }
321        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
322        int currentUser = ActivityManager.getCurrentUser();
323        if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
324        for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
325            if (!tileSpecs.contains(tile.getKey())) {
326                if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
327                tile.getValue().destroy();
328            }
329        }
330        final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
331        for (String tileSpec : tileSpecs) {
332            QSTile<?> tile = mTiles.get(tileSpec);
333            if (tile != null && (!(tile instanceof CustomTile)
334                    || ((CustomTile) tile).getUser() == currentUser)) {
335                if (DEBUG) Log.d(TAG, "Adding " + tile);
336                tile.removeCallbacks();
337                if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
338                    tile.userSwitch(currentUser);
339                }
340                newTiles.put(tileSpec, tile);
341            } else {
342                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
343                try {
344                    tile = createTile(tileSpec);
345                    if (tile != null && tile.isAvailable()) {
346                        tile.setTileSpec(tileSpec);
347                        newTiles.put(tileSpec, tile);
348                    }
349                } catch (Throwable t) {
350                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
351                }
352            }
353        }
354        mCurrentUser = currentUser;
355        mTileSpecs.clear();
356        mTileSpecs.addAll(tileSpecs);
357        mTiles.clear();
358        mTiles.putAll(newTiles);
359        for (int i = 0; i < mCallbacks.size(); i++) {
360            mCallbacks.get(i).onTilesChanged();
361        }
362    }
363
364    @Override
365    public void removeTile(String tileSpec) {
366        ArrayList<String> specs = new ArrayList<>(mTileSpecs);
367        specs.remove(tileSpec);
368        Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
369                TextUtils.join(",", specs), ActivityManager.getCurrentUser());
370    }
371
372    public void addTile(String spec) {
373        final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
374                TILES_SETTING, ActivityManager.getCurrentUser());
375        final List<String> tileSpecs = loadTileSpecs(mContext, setting);
376        if (tileSpecs.contains(spec)) {
377            return;
378        }
379        tileSpecs.add(spec);
380        Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
381                TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser());
382    }
383
384    public void addTile(ComponentName tile) {
385        List<String> newSpecs = new ArrayList<>(mTileSpecs);
386        newSpecs.add(0, CustomTile.toSpec(tile));
387        changeTiles(mTileSpecs, newSpecs);
388    }
389
390    public void removeTile(ComponentName tile) {
391        List<String> newSpecs = new ArrayList<>(mTileSpecs);
392        newSpecs.remove(CustomTile.toSpec(tile));
393        changeTiles(mTileSpecs, newSpecs);
394    }
395
396    public void changeTiles(List<String> previousTiles, List<String> newTiles) {
397        final int NP = previousTiles.size();
398        final int NA = newTiles.size();
399        for (int i = 0; i < NP; i++) {
400            String tileSpec = previousTiles.get(i);
401            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
402            if (!newTiles.contains(tileSpec)) {
403                ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
404                Intent intent = new Intent().setComponent(component);
405                TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(),
406                        mContext, mServices, new Tile(), intent,
407                        new UserHandle(ActivityManager.getCurrentUser()));
408                lifecycleManager.onStopListening();
409                lifecycleManager.onTileRemoved();
410                TileLifecycleManager.setTileAdded(mContext, component, false);
411                lifecycleManager.flushMessagesAndUnbind();
412            }
413        }
414        if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
415        Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
416                TextUtils.join(",", newTiles), ActivityManager.getCurrentUser());
417    }
418
419    public QSTile<?> createTile(String tileSpec) {
420        if (tileSpec.equals("wifi")) return new WifiTile(this);
421        else if (tileSpec.equals("bt")) return new BluetoothTile(this);
422        else if (tileSpec.equals("cell")) return new CellularTile(this);
423        else if (tileSpec.equals("dnd")) return new DndTile(this);
424        else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
425        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
426        else if (tileSpec.equals("work")) return new WorkModeTile(this);
427        else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
428        else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
429        else if (tileSpec.equals("location")) return new LocationTile(this);
430        else if (tileSpec.equals("cast")) return new CastTile(this);
431        else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
432        else if (tileSpec.equals("user")) return new UserTile(this);
433        else if (tileSpec.equals("battery")) return new BatteryTile(this);
434        else if (tileSpec.equals("saver")) return new DataSaverTile(this);
435        else if (tileSpec.equals("night")) return new NightDisplayTile(this);
436        // Intent tiles.
437        else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
438        else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
439        else {
440            Log.w(TAG, "Bad tile spec: " + tileSpec);
441            return null;
442        }
443    }
444
445    protected List<String> loadTileSpecs(Context context, String tileList) {
446        final Resources res = context.getResources();
447        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
448        if (tileList == null) {
449            tileList = res.getString(R.string.quick_settings_tiles);
450            if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
451        } else {
452            if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
453        }
454        final ArrayList<String> tiles = new ArrayList<String>();
455        boolean addedDefault = false;
456        for (String tile : tileList.split(",")) {
457            tile = tile.trim();
458            if (tile.isEmpty()) continue;
459            if (tile.equals("default")) {
460                if (!addedDefault) {
461                    tiles.addAll(Arrays.asList(defaultTileList.split(",")));
462                    addedDefault = true;
463                }
464            } else {
465                tiles.add(tile);
466            }
467        }
468        return tiles;
469    }
470}
471