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.content.Context;
20import android.content.Intent;
21import android.content.res.Resources;
22import android.database.ContentObserver;
23import android.net.Uri;
24import android.os.Process;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.os.Looper;
28import android.provider.Settings.Secure;
29import android.util.Log;
30
31import com.android.systemui.R;
32import com.android.systemui.qs.QSTile;
33import com.android.systemui.qs.tiles.AirplaneModeTile;
34import com.android.systemui.qs.tiles.BluetoothTile;
35import com.android.systemui.qs.tiles.CastTile;
36import com.android.systemui.qs.tiles.CellularTile;
37import com.android.systemui.qs.tiles.ColorInversionTile;
38import com.android.systemui.qs.tiles.FlashlightTile;
39import com.android.systemui.qs.tiles.HotspotTile;
40import com.android.systemui.qs.tiles.IntentTile;
41import com.android.systemui.qs.tiles.LocationTile;
42import com.android.systemui.qs.tiles.RotationLockTile;
43import com.android.systemui.qs.tiles.WifiTile;
44import com.android.systemui.settings.CurrentUserTracker;
45import com.android.systemui.statusbar.policy.BluetoothController;
46import com.android.systemui.statusbar.policy.CastController;
47import com.android.systemui.statusbar.policy.FlashlightController;
48import com.android.systemui.statusbar.policy.KeyguardMonitor;
49import com.android.systemui.statusbar.policy.LocationController;
50import com.android.systemui.statusbar.policy.NetworkController;
51import com.android.systemui.statusbar.policy.RotationLockController;
52import com.android.systemui.statusbar.policy.HotspotController;
53import com.android.systemui.statusbar.policy.SecurityController;
54import com.android.systemui.statusbar.policy.UserSwitcherController;
55import com.android.systemui.statusbar.policy.ZenModeController;
56
57import java.util.ArrayList;
58import java.util.Arrays;
59import java.util.Collection;
60import java.util.LinkedHashMap;
61import java.util.List;
62import java.util.Map;
63
64/** Platform implementation of the quick settings tile host **/
65public class QSTileHost implements QSTile.Host {
66    private static final String TAG = "QSTileHost";
67    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
68
69    private static final String TILES_SETTING = "sysui_qs_tiles";
70
71    private final Context mContext;
72    private final PhoneStatusBar mStatusBar;
73    private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
74    private final Observer mObserver = new Observer();
75    private final BluetoothController mBluetooth;
76    private final LocationController mLocation;
77    private final RotationLockController mRotation;
78    private final NetworkController mNetwork;
79    private final ZenModeController mZen;
80    private final HotspotController mHotspot;
81    private final CastController mCast;
82    private final Looper mLooper;
83    private final CurrentUserTracker mUserTracker;
84    private final FlashlightController mFlashlight;
85    private final UserSwitcherController mUserSwitcherController;
86    private final KeyguardMonitor mKeyguard;
87    private final SecurityController mSecurity;
88
89    private Callback mCallback;
90
91    public QSTileHost(Context context, PhoneStatusBar statusBar,
92            BluetoothController bluetooth, LocationController location,
93            RotationLockController rotation, NetworkController network,
94            ZenModeController zen, HotspotController hotspot,
95            CastController cast, FlashlightController flashlight,
96            UserSwitcherController userSwitcher, KeyguardMonitor keyguard,
97            SecurityController security) {
98        mContext = context;
99        mStatusBar = statusBar;
100        mBluetooth = bluetooth;
101        mLocation = location;
102        mRotation = rotation;
103        mNetwork = network;
104        mZen = zen;
105        mHotspot = hotspot;
106        mCast = cast;
107        mFlashlight = flashlight;
108        mUserSwitcherController = userSwitcher;
109        mKeyguard = keyguard;
110        mSecurity = security;
111
112        final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
113                Process.THREAD_PRIORITY_BACKGROUND);
114        ht.start();
115        mLooper = ht.getLooper();
116
117        mUserTracker = new CurrentUserTracker(mContext) {
118            @Override
119            public void onUserSwitched(int newUserId) {
120                recreateTiles();
121                for (QSTile<?> tile : mTiles.values()) {
122                    tile.userSwitch(newUserId);
123                }
124                mSecurity.onUserSwitched(newUserId);
125                mNetwork.onUserSwitched(newUserId);
126                mObserver.register();
127            }
128        };
129        recreateTiles();
130
131        mUserTracker.startTracking();
132        mObserver.register();
133    }
134
135    @Override
136    public void setCallback(Callback callback) {
137        mCallback = callback;
138    }
139
140    @Override
141    public Collection<QSTile<?>> getTiles() {
142        return mTiles.values();
143    }
144
145    @Override
146    public void startSettingsActivity(final Intent intent) {
147        mStatusBar.postStartSettingsActivity(intent, 0);
148    }
149
150    @Override
151    public void warn(String message, Throwable t) {
152        // already logged
153    }
154
155    @Override
156    public void collapsePanels() {
157        mStatusBar.postAnimateCollapsePanels();
158    }
159
160    @Override
161    public Looper getLooper() {
162        return mLooper;
163    }
164
165    @Override
166    public Context getContext() {
167        return mContext;
168    }
169
170    @Override
171    public BluetoothController getBluetoothController() {
172        return mBluetooth;
173    }
174
175    @Override
176    public LocationController getLocationController() {
177        return mLocation;
178    }
179
180    @Override
181    public RotationLockController getRotationLockController() {
182        return mRotation;
183    }
184
185    @Override
186    public NetworkController getNetworkController() {
187        return mNetwork;
188    }
189
190    @Override
191    public ZenModeController getZenModeController() {
192        return mZen;
193    }
194
195    @Override
196    public HotspotController getHotspotController() {
197        return mHotspot;
198    }
199
200    @Override
201    public CastController getCastController() {
202        return mCast;
203    }
204
205    @Override
206    public FlashlightController getFlashlightController() {
207        return mFlashlight;
208    }
209
210    @Override
211    public KeyguardMonitor getKeyguardMonitor() {
212        return mKeyguard;
213    }
214
215    public UserSwitcherController getUserSwitcherController() {
216        return mUserSwitcherController;
217    }
218
219    public SecurityController getSecurityController() {
220        return mSecurity;
221    }
222
223    private void recreateTiles() {
224        if (DEBUG) Log.d(TAG, "Recreating tiles");
225        final List<String> tileSpecs = loadTileSpecs();
226        for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
227            if (!tileSpecs.contains(tile.getKey())) {
228                if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
229                tile.getValue().destroy();
230            }
231        }
232        final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
233        for (String tileSpec : tileSpecs) {
234            if (mTiles.containsKey(tileSpec)) {
235                newTiles.put(tileSpec, mTiles.get(tileSpec));
236            } else {
237                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
238                try {
239                    newTiles.put(tileSpec, createTile(tileSpec));
240                } catch (Throwable t) {
241                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
242                }
243            }
244        }
245        if (mTiles.equals(newTiles)) return;
246        mTiles.clear();
247        mTiles.putAll(newTiles);
248        if (mCallback != null) {
249            mCallback.onTilesChanged();
250        }
251    }
252
253    private QSTile<?> createTile(String tileSpec) {
254        if (tileSpec.equals("wifi")) return new WifiTile(this);
255        else if (tileSpec.equals("bt")) return new BluetoothTile(this);
256        else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
257        else if (tileSpec.equals("cell")) return new CellularTile(this);
258        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
259        else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
260        else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
261        else if (tileSpec.equals("location")) return new LocationTile(this);
262        else if (tileSpec.equals("cast")) return new CastTile(this);
263        else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
264        else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
265        else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
266    }
267
268    private List<String> loadTileSpecs() {
269        final Resources res = mContext.getResources();
270        final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
271        String tileList = Secure.getStringForUser(mContext.getContentResolver(), TILES_SETTING,
272                mUserTracker.getCurrentUserId());
273        if (tileList == null) {
274            tileList = res.getString(R.string.quick_settings_tiles);
275            if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
276        } else {
277            if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
278        }
279        final ArrayList<String> tiles = new ArrayList<String>();
280        boolean addedDefault = false;
281        for (String tile : tileList.split(",")) {
282            tile = tile.trim();
283            if (tile.isEmpty()) continue;
284            if (tile.equals("default")) {
285                if (!addedDefault) {
286                    tiles.addAll(Arrays.asList(defaultTileList.split(",")));
287                    addedDefault = true;
288                }
289            } else {
290                tiles.add(tile);
291            }
292        }
293        return tiles;
294    }
295
296    private class Observer extends ContentObserver {
297        private boolean mRegistered;
298
299        public Observer() {
300            super(new Handler(Looper.getMainLooper()));
301        }
302
303        public void register() {
304            if (mRegistered) {
305                mContext.getContentResolver().unregisterContentObserver(this);
306            }
307            mContext.getContentResolver().registerContentObserver(Secure.getUriFor(TILES_SETTING),
308                    false, this, mUserTracker.getCurrentUserId());
309            mRegistered = true;
310        }
311
312        @Override
313        public void onChange(boolean selfChange, Uri uri) {
314            recreateTiles();
315        }
316    }
317}
318