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