summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
authorJohn Spurlock <jspurlock@google.com>2014-08-10 18:04:16 -0400
committerJohn Spurlock <jspurlock@google.com>2014-08-11 10:19:21 -0400
commitbceed060f0090a4f86418c4515128d5ec8ebdd4a (patch)
treee772b34120b40d046a370aa9fb9c3e253011c131 /packages/SystemUI/src
parent88992bc90c874eb0eb159b3ea37659a8b83bbee0 (diff)
downloadframeworks_base-bceed060f0090a4f86418c4515128d5ec8ebdd4a.zip
frameworks_base-bceed060f0090a4f86418c4515128d5ec8ebdd4a.tar.gz
frameworks_base-bceed060f0090a4f86418c4515128d5ec8ebdd4a.tar.bz2
QS: Fix some QS layout issues.
- Make the tile list configurable for testing. - Support an external tile backed by a sticky broadcast intent. - Ensure tiles clean up properly when no longer needed. Bug:16818269 Bug:16822505 Change-Id: Ie24f878aae0d19c7f1feca4c519d10667023bef3
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java143
11 files changed, 381 insertions, 47 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 3679b4c..cbf6e29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -37,6 +37,7 @@ import com.android.systemui.settings.ToggleSlider;
import com.android.systemui.statusbar.phone.QSTileHost;
import java.util.ArrayList;
+import java.util.Collection;
/** View that represents the quick settings tile panel. **/
public class QSPanel extends ViewGroup {
@@ -186,12 +187,25 @@ public class QSPanel extends ViewGroup {
v.setVisibility(visible ? VISIBLE : GONE);
}
- public void addTile(final QSTile<?> tile) {
+ public void setTiles(Collection<QSTile<?>> tiles) {
+ for (TileRecord record : mRecords) {
+ removeView(record.tileView);
+ }
+ mRecords.clear();
+ for (QSTile<?> tile : tiles) {
+ addTile(tile);
+ }
+ if (isShowingDetail()) {
+ mDetail.bringToFront();
+ }
+ }
+
+ private void addTile(final QSTile<?> tile) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = tile.createTileView(mContext);
r.tileView.setVisibility(View.GONE);
- r.tile.setCallback(new QSTile.Callback() {
+ final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
setTileVisibility(r.tileView, state.visible);
@@ -213,7 +227,8 @@ public class QSPanel extends ViewGroup {
fireScanStateChanged(state);
}
}
- });
+ };
+ r.tile.setCallback(callback);
final View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -227,6 +242,8 @@ public class QSPanel extends ViewGroup {
}
};
r.tileView.init(click, clickSecondary);
+ r.tile.setListening(mListening);
+ callback.onStateChanged(r.tile.getState());
r.tile.refreshState();
mRecords.add(r);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 93766af..6975541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -37,9 +37,8 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.volume.VolumeComponent;
-import java.util.List;
+import java.util.Collection;
import java.util.Objects;
/**
@@ -134,6 +133,14 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
}
+ public void destroy() {
+ mHandler.sendEmptyMessage(H.DESTROY);
+ }
+
+ public TState getState() {
+ return mState;
+ }
+
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -181,6 +188,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
handleRefreshState(null);
}
+ protected void handleDestroy() {
+ setListening(false);
+ mCallback = null;
+ }
+
protected final class H extends Handler {
private static final int SET_CALLBACK = 1;
private static final int CLICK = 2;
@@ -190,6 +202,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
private static final int USER_SWITCH = 6;
private static final int TOGGLE_STATE_CHANGED = 7;
private static final int SCAN_STATE_CHANGED = 8;
+ private static final int DESTROY = 9;
private H(Looper looper) {
super(looper);
@@ -223,6 +236,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
} else if (msg.what == SCAN_STATE_CHANGED) {
name = "handleScanStateChanged";
handleScanStateChanged(msg.arg1 != 0);
+ } else if (msg.what == DESTROY) {
+ name = "handleDestroy";
+ handleDestroy();
+ } else {
+ throw new IllegalArgumentException("Unknown msg: " + msg.what);
}
} catch (Throwable t) {
final String error = "Error in " + name;
@@ -245,7 +263,8 @@ public abstract class QSTile<TState extends State> implements Listenable {
void collapsePanels();
Looper getLooper();
Context getContext();
- List<QSTile<?>> getTiles();
+ Collection<QSTile<?>> getTiles();
+ void setCallback(Callback callback);
BluetoothController getBluetoothController();
LocationController getLocationController();
RotationLockController getRotationLockController();
@@ -255,6 +274,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
CastController getCastController();
FlashlightController getFlashlightController();
KeyguardMonitor getKeyguardMonitor();
+
+ public interface Callback {
+ void onTilesChanged();
+ }
}
public static class State {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index e72d3a9..ad79aba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -23,8 +23,9 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.Listenable;
-public class UsageTracker {
+public class UsageTracker implements Listenable {
private static final long MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
private final Context mContext;
@@ -32,7 +33,7 @@ public class UsageTracker {
private final String mPrefKey;
private final String mResetAction;
- private BroadcastReceiver mReceiver;
+ private boolean mRegistered;
public UsageTracker(Context context, Class<?> tile) {
mContext = context;
@@ -42,17 +43,14 @@ public class UsageTracker {
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
- public void listenForReset() {
- if (mReceiver != null) {
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mResetAction.equals(intent.getAction())) {
- reset();
- }
- }
- };
- mContext.registerReceiver(mReceiver, new IntentFilter(mResetAction));
+ @Override
+ public void setListening(boolean listen) {
+ if (listen && !mRegistered) {
+ mContext.registerReceiver(mReceiver, new IntentFilter(mResetAction));
+ mRegistered = true;
+ } else if (!listen && mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mRegistered = false;
}
}
@@ -72,4 +70,13 @@ public class UsageTracker {
private SharedPreferences getSharedPrefs() {
return mContext.getSharedPreferences(mContext.getPackageName(), 0);
}
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mResetAction.equals(intent.getAction())) {
+ reset();
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 191bac9..5d1fa80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -31,6 +31,8 @@ import com.android.systemui.qs.QSTile;
public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
private final GlobalSetting mSetting;
+ private boolean mListening;
+
public AirplaneModeTile(Host host) {
super(host);
@@ -79,6 +81,8 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
}
public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
if (listening) {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 9c88466..21254d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -41,7 +41,13 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
}
};
mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class);
- mUsageTracker.listenForReset();
+ mUsageTracker.setListening(true);
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mUsageTracker.setListening(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index f4ddd84..3ddf5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -16,18 +16,13 @@
package com.android.systemui.qs.tiles;
+import android.app.ActivityManager;
+import android.os.SystemClock;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.SecureSetting;
import com.android.systemui.statusbar.policy.FlashlightController;
-import android.app.ActivityManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings.Secure;
-import android.util.Log;
-
/** Quick settings tile: Control flashlight **/
public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
FlashlightController.FlashlightListener {
@@ -46,6 +41,12 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
}
@Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mFlashlightController.removeListener(this);
+ }
+
+ @Override
protected BooleanState newTileState() {
return new BooleanState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index ff26b54..96333a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -35,7 +35,13 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
super(host);
mController = host.getHotspotController();
mUsageTracker = new UsageTracker(host.getContext(), HotspotTile.class);
- mUsageTracker.listenForReset();
+ mUsageTracker.setListening(true);
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mUsageTracker.setListening(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
new file mode 100644
index 0000000..58587e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.qs.QSTile;
+
+public class IntentTile extends QSTile<QSTile.State> {
+ public static final String PREFIX = "intent(";
+
+ private PendingIntent mOnClick;
+ private String mOnClickUri;
+ private int mCurrentUserId;
+
+ private IntentTile(Host host, String action) {
+ super(host);
+ mContext.registerReceiver(mReceiver, new IntentFilter(action));
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ public static QSTile<?> create(Host host, String spec) {
+ if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+ throw new IllegalArgumentException("Bad intent tile spec: " + spec);
+ }
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty intent tile spec action");
+ }
+ return new IntentTile(host, action);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ super.handleUserSwitch(newUserId);
+ mCurrentUserId = newUserId;
+ }
+
+ @Override
+ protected void handleClick() {
+ try {
+ if (mOnClick != null) {
+ mOnClick.send();
+ } else if (mOnClickUri != null) {
+ final Intent intent = Intent.parseUri(mOnClickUri, Intent.URI_INTENT_SCHEME);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "Error sending click intent", t);
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ if (!(arg instanceof Intent)) return;
+ final Intent intent = (Intent) arg;
+ state.visible = intent.getBooleanExtra("visible", true);
+ state.contentDescription = intent.getStringExtra("contentDescription");
+ state.label = intent.getStringExtra("label");
+ state.iconId = 0;
+ state.icon = null;
+ final byte[] iconBitmap = intent.getByteArrayExtra("iconBitmap");
+ if (iconBitmap != null) {
+ try {
+ final Bitmap b = BitmapFactory.decodeByteArray(iconBitmap, 0, iconBitmap.length);
+ state.icon = new BitmapDrawable(mContext.getResources(), b);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error loading icon bitmap, length " + iconBitmap.length, t);
+ }
+ } else {
+ final int iconId = intent.getIntExtra("iconId", 0);
+ if (iconId != 0) {
+ final String iconPackage = intent.getStringExtra("iconPackage");
+ if (!TextUtils.isEmpty(iconPackage)) {
+ state.icon = getPackageDrawable(iconPackage, iconId);
+ } else {
+ state.iconId = iconId;
+ }
+ }
+ }
+ mOnClick = intent.getParcelableExtra("onClick");
+ mOnClickUri = intent.getStringExtra("onClickUri");
+ }
+
+ private Drawable getPackageDrawable(String pkg, int id) {
+ try {
+ return mContext.createPackageContext(pkg, 0).getDrawable(id);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error loading package drawable pkg=" + pkg + " id=" + id, t);
+ return null;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ refreshState(intent);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 74ae4a4..a07bc5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -197,6 +197,20 @@ public class NotificationPanelView extends PanelView implements
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
mSecureCameraLaunchManager =
new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
+
+ // recompute internal state when qspanel height changes
+ mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight,
+ int oldBottom) {
+ final int height = bottom - top;
+ final int oldHeight = oldBottom - oldTop;
+ if (height != oldHeight) {
+ onScrollChanged();
+ }
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5eb45df..a2f8931 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -818,10 +818,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSwitcherController, mKeyguardMonitor,
mSecurityController);
mQSPanel.setHost(qsh);
- for (QSTile<?> tile : qsh.getTiles()) {
- mQSPanel.addTile(tile);
- }
+ mQSPanel.setTiles(qsh.getTiles());
mHeader.setQSPanel(mQSPanel);
+ qsh.setCallback(new QSTileHost.Callback() {
+ @Override
+ public void onTilesChanged() {
+ mQSPanel.setTiles(qsh.getTiles());
+ }
+ });
}
mBackdrop = (FrameLayout) mStatusBarWindow.findViewById(R.id.backdrop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 8f25fb9..729d459 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -18,9 +18,16 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.provider.Settings.Secure;
+import android.util.Log;
+import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
import com.android.systemui.qs.tiles.BluetoothTile;
@@ -29,6 +36,7 @@ import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
+import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.WifiTile;
@@ -46,13 +54,23 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/** Platform implementation of the quick settings tile host **/
public class QSTileHost implements QSTile.Host {
+ private static final String TAG = "QSTileHost";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String TILES_SETTING = "sysui_qs_tiles";
private final Context mContext;
private final PhoneStatusBar mStatusBar;
+ private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
+ private final Observer mObserver = new Observer();
private final BluetoothController mBluetooth;
private final LocationController mLocation;
private final RotationLockController mRotation;
@@ -62,12 +80,13 @@ public class QSTileHost implements QSTile.Host {
private final CastController mCast;
private final Looper mLooper;
private final CurrentUserTracker mUserTracker;
- private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
private final FlashlightController mFlashlight;
private final UserSwitcherController mUserSwitcherController;
private final KeyguardMonitor mKeyguard;
private final SecurityController mSecurity;
+ private Callback mCallback;
+
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
RotationLockController rotation, NetworkController network,
@@ -93,31 +112,30 @@ public class QSTileHost implements QSTile.Host {
ht.start();
mLooper = ht.getLooper();
- mTiles.add(new WifiTile(this));
- mTiles.add(new BluetoothTile(this));
- mTiles.add(new ColorInversionTile(this));
- mTiles.add(new CellularTile(this));
- mTiles.add(new AirplaneModeTile(this));
- mTiles.add(new RotationLockTile(this));
- mTiles.add(new FlashlightTile(this));
- mTiles.add(new LocationTile(this));
- mTiles.add(new CastTile(this));
- mTiles.add(new HotspotTile(this));
-
mUserTracker = new CurrentUserTracker(mContext) {
@Override
public void onUserSwitched(int newUserId) {
- for (QSTile<?> tile : mTiles) {
+ recreateTiles();
+ for (QSTile<?> tile : mTiles.values()) {
tile.userSwitch(newUserId);
}
+ mObserver.register();
}
};
+ recreateTiles();
+
mUserTracker.startTracking();
+ mObserver.register();
+ }
+
+ @Override
+ public void setCallback(Callback callback) {
+ mCallback = callback;
}
@Override
- public List<QSTile<?>> getTiles() {
- return mTiles;
+ public Collection<QSTile<?>> getTiles() {
+ return mTiles.values();
}
@Override
@@ -197,4 +215,99 @@ public class QSTileHost implements QSTile.Host {
public SecurityController getSecurityController() {
return mSecurity;
}
+
+ private void recreateTiles() {
+ if (DEBUG) Log.d(TAG, "Recreating tiles");
+ final List<String> tileSpecs = loadTileSpecs();
+ for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
+ if (!tileSpecs.contains(tile.getKey())) {
+ if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+ tile.getValue().destroy();
+ }
+ }
+ final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
+ for (String tileSpec : tileSpecs) {
+ if (mTiles.containsKey(tileSpec)) {
+ newTiles.put(tileSpec, mTiles.get(tileSpec));
+ } else {
+ if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
+ try {
+ newTiles.put(tileSpec, createTile(tileSpec));
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
+ }
+ }
+ }
+ if (mTiles.equals(newTiles)) return;
+ mTiles.clear();
+ mTiles.putAll(newTiles);
+ if (mCallback != null) {
+ mCallback.onTilesChanged();
+ }
+ }
+
+ private QSTile<?> createTile(String tileSpec) {
+ if (tileSpec.equals("wifi")) return new WifiTile(this);
+ else if (tileSpec.equals("bt")) return new BluetoothTile(this);
+ else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
+ else if (tileSpec.equals("cell")) return new CellularTile(this);
+ else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
+ else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
+ else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
+ else if (tileSpec.equals("location")) return new LocationTile(this);
+ else if (tileSpec.equals("cast")) return new CastTile(this);
+ else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
+ else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
+ else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
+ }
+
+ private List<String> loadTileSpecs() {
+ final Resources res = mContext.getResources();
+ final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+ String tileList = Secure.getStringForUser(mContext.getContentResolver(), TILES_SETTING,
+ mUserTracker.getCurrentUserId());
+ if (tileList == null) {
+ tileList = res.getString(R.string.quick_settings_tiles);
+ if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
+ } else {
+ if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
+ }
+ final ArrayList<String> tiles = new ArrayList<String>();
+ boolean addedDefault = false;
+ for (String tile : tileList.split(",")) {
+ tile = tile.trim();
+ if (tile.isEmpty()) continue;
+ if (tile.equals("default")) {
+ if (!addedDefault) {
+ tiles.addAll(Arrays.asList(defaultTileList.split(",")));
+ addedDefault = true;
+ }
+ } else {
+ tiles.add(tile);
+ }
+ }
+ return tiles;
+ }
+
+ private class Observer extends ContentObserver {
+ private boolean mRegistered;
+
+ public Observer() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ public void register() {
+ if (mRegistered) {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ mContext.getContentResolver().registerContentObserver(Secure.getUriFor(TILES_SETTING),
+ false, this, mUserTracker.getCurrentUserId());
+ mRegistered = true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ recreateTiles();
+ }
+ }
}