Merge "Add Quick Settings API"
diff --git a/Android.mk b/Android.mk
index cc0749c5..9d2ca0d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -418,6 +418,8 @@
packages/services/PacProcessor/com/android/net/IProxyService.aidl \
packages/services/Proxy/com/android/net/IProxyCallback.aidl \
packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
+ core/java/android/service/quicksettings/IQSService.aidl \
+ core/java/android/service/quicksettings/IQSTileService.aidl \
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
@@ -626,6 +628,7 @@
frameworks/base/core/java/android/bluetooth/le/ScanResult.aidl \
frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
frameworks/base/core/java/android/database/CursorWindow.aidl \
+ frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
$(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/api/current.txt b/api/current.txt
index 2d59de1..8bac46d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30,6 +30,7 @@
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -29051,6 +29052,35 @@
}
+package android.service.quicksettings {
+
+ public final class Tile implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.CharSequence getContentDescription();
+ method public android.graphics.drawable.Icon getIcon();
+ method public java.lang.CharSequence getLabel();
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setIcon(android.graphics.drawable.Icon);
+ method public void setLabel(java.lang.CharSequence);
+ method public void updateTile();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+ }
+
+ public class TileService extends android.app.Service {
+ ctor public TileService();
+ method public final android.service.quicksettings.Tile getQsTile();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void onClick();
+ method public void onStartListening();
+ method public void onStopListening();
+ method public void onTileAdded();
+ method public void onTileRemoved();
+ field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+ }
+
+}
+
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
diff --git a/api/system-current.txt b/api/system-current.txt
index 4719988..3a4706b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43,6 +43,7 @@
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -31199,6 +31200,35 @@
}
+package android.service.quicksettings {
+
+ public final class Tile implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.CharSequence getContentDescription();
+ method public android.graphics.drawable.Icon getIcon();
+ method public java.lang.CharSequence getLabel();
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setIcon(android.graphics.drawable.Icon);
+ method public void setLabel(java.lang.CharSequence);
+ method public void updateTile();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+ }
+
+ public class TileService extends android.app.Service {
+ ctor public TileService();
+ method public final android.service.quicksettings.Tile getQsTile();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void onClick();
+ method public void onStartListening();
+ method public void onStopListening();
+ method public void onTileAdded();
+ method public void onTileRemoved();
+ field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+ }
+
+}
+
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
new file mode 100644
index 0000000..087eb61
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015, 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+/**
+ * @hide
+ */
+interface IQSService {
+ void updateQsTile(in Tile tile);
+}
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
new file mode 100644
index 0000000..6b46bee5
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015, 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 android.service.quicksettings;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.IQSService;
+
+/**
+ * @hide
+ */
+oneway interface IQSTileService {
+ void setQSTile(in Tile tile);
+ void onTileAdded();
+ void onTileRemoved();
+ void onStartListening();
+ void onStopListening();
+ void onClick();
+}
diff --git a/core/java/android/service/quicksettings/Tile.aidl b/core/java/android/service/quicksettings/Tile.aidl
new file mode 100644
index 0000000..0373326
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, 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 android.service.quicksettings;
+
+parcelable Tile;
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
new file mode 100644
index 0000000..c8ae171
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A Tile holds the state of a tile that will be displayed
+ * in Quick Settings.
+ *
+ * A tile in Quick Settings exists as an icon with an accompanied label.
+ * It also may have content description for accessibility usability.
+ * The style and layout of the tile may change to match a given
+ * device.
+ */
+public final class Tile implements Parcelable {
+
+ private static final String TAG = "Tile";
+
+ private ComponentName mComponentName;
+ private IQSService mService;
+ private Icon mIcon;
+ private CharSequence mLabel;
+ private CharSequence mContentDescription;
+
+ /**
+ * @hide
+ */
+ public Tile(Parcel source) {
+ readFromParcel(source);
+ }
+
+ /**
+ * @hide
+ */
+ public Tile(ComponentName componentName, IQSService service) {
+ mComponentName = componentName;
+ mService = service;
+ }
+
+ /**
+ * @hide
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Gets the current icon for the tile.
+ */
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Sets the current icon for the tile.
+ *
+ * This icon is expected to be white on alpha, and may be
+ * tinted by the system to match it's theme.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param icon New icon to show.
+ */
+ public void setIcon(Icon icon) {
+ this.mIcon = icon;
+ }
+
+ /**
+ * Gets the current label for the tile.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Sets the current label for the tile.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param label New label to show.
+ */
+ public void setLabel(CharSequence label) {
+ this.mLabel = label;
+ }
+
+ /**
+ * Gets the current content description for the tile.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the current content description for the tile.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param contentDescription New content description to use.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ this.mContentDescription = contentDescription;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Pushes the state of the Tile to Quick Settings to be displayed.
+ */
+ public void updateTile() {
+ try {
+ mService.updateQsTile(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't update tile");
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongInterface(mService);
+ if (mComponentName != null) {
+ dest.writeByte((byte) 1);
+ mComponentName.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mIcon != null) {
+ dest.writeByte((byte) 1);
+ mIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ TextUtils.writeToParcel(mContentDescription, dest, flags);
+ }
+
+ private void readFromParcel(Parcel source) {
+ mService = IQSService.Stub.asInterface(source.readStrongBinder());
+ if (source.readByte() != 0) {
+ mComponentName = ComponentName.CREATOR.createFromParcel(source);
+ } else {
+ mComponentName = null;
+ }
+ if (source.readByte() != 0) {
+ mIcon = Icon.CREATOR.createFromParcel(source);
+ } else {
+ mIcon = null;
+ }
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ public static final Creator<Tile> CREATOR = new Creator<Tile>() {
+ @Override
+ public Tile createFromParcel(Parcel source) {
+ return new Tile(source);
+ }
+
+ @Override
+ public Tile[] newArray(int size) {
+ return new Tile[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
new file mode 100644
index 0000000..eba4c6f
--- /dev/null
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 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 android.service.quicksettings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A QSTileService provides the user a tile that can be added to Quick Settings.
+ * Quick Settings is a space provided that allows the user to change settings and
+ * take quick actions without leaving the context of their current app.
+ *
+ * <p>The lifecycle of a QSTileService is different from some other services in
+ * that it may be unbound during parts of its lifecycle. Any of the following
+ * lifecycle events can happen indepently in a separate binding/creation of the
+ * service.</p>
+ *
+ * <ul>
+ * <li>When a tile is added by the user its QSTileService will be bound to and
+ * {@link #onTileAdded()} will be called.</li>
+ *
+ * <li>When a tile should be up to date and listing will be indicated by
+ * {@link #onStartListening()} and {@link #onStopListening()}.</li>
+ *
+ * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
+ * will be called.</li>
+ * </ul>
+ * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
+ * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
+ * The label and icon for the service will be used as the default label and
+ * icon for the tile. Here is an example QSTileService declaration.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ * android:name=".MyQSTileService"
+ * android:label="@string/my_default_tile_label"
+ * android:icon="@drawable/my_default_icon_label"
+ * android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ * <intent-filter>
+ * <action android:name="android.intent.action.QS_TILE" />
+ * </intent-filter>
+ * </service>}
+ * </pre>
+ *
+ * @see Tile Tile for details about the UI of a Quick Settings Tile.
+ */
+public class TileService extends Service {
+
+ /**
+ * Action that identifies a Service as being a QSTileService.
+ */
+ public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+
+ private final H mHandler = new H(Looper.getMainLooper());
+
+ private boolean mListening = false;
+ private Tile mTile;
+
+ /**
+ * Called when the user adds this tile to Quick Settings.
+ * <p/>
+ * Note that this is not guaranteed to be called between {@link #onCreate()}
+ * and {@link #onStartListening()}, it will only be called when the tile is added
+ * and not on subsequent binds.
+ */
+ public void onTileAdded() {
+ }
+
+ /**
+ * Called when the user removes this tile from Quick Settings.
+ */
+ public void onTileRemoved() {
+ }
+
+ /**
+ * Called when this tile moves into a listening state.
+ * <p/>
+ * When this tile is in a listening state it is expected to keep the
+ * UI up to date. Any listeners or callbacks needed to keep this tile
+ * up to date should be registered here and unregistered in {@link #onStopListening()}.
+ *
+ * @see #getQsTile()
+ * @see Tile#updateTile()
+ */
+ public void onStartListening() {
+ }
+
+ /**
+ * Called when this tile moves out of the listening state.
+ */
+ public void onStopListening() {
+ }
+
+ /**
+ * Called when the user clicks on this tile.
+ */
+ public void onClick() {
+ }
+
+ /**
+ * Gets the {@link Tile} for this service.
+ * <p/>
+ * This tile may be used to get or set the current state for this
+ * tile. This tile is only valid for updates between {@link #onStartListening()}
+ * and {@link #onStopListening()}.
+ */
+ public final Tile getQsTile() {
+ return mTile;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IQSTileService.Stub() {
+ @Override
+ public void setQSTile(Tile tile) throws RemoteException {
+ mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
+ }
+
+ @Override
+ public void onTileRemoved() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
+ }
+
+ @Override
+ public void onTileAdded() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
+ }
+
+ @Override
+ public void onStopListening() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
+ }
+
+ @Override
+ public void onStartListening() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
+ }
+
+ @Override
+ public void onClick() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED);
+ }
+ };
+ }
+
+ private class H extends Handler {
+ private static final int MSG_SET_TILE = 1;
+ private static final int MSG_START_LISTENING = 2;
+ private static final int MSG_STOP_LISTENING = 3;
+ private static final int MSG_TILE_ADDED = 4;
+ private static final int MSG_TILE_REMOVED = 5;
+ private static final int MSG_TILE_CLICKED = 6;
+
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_TILE:
+ mTile = (Tile) msg.obj;
+ break;
+ case MSG_TILE_ADDED:
+ TileService.this.onTileRemoved();
+ break;
+ case MSG_TILE_REMOVED:
+ TileService.this.onTileAdded();
+ break;
+ case MSG_START_LISTENING:
+ if (mListening) {
+ mListening = false;
+ TileService.this.onStopListening();
+ }
+ break;
+ case MSG_STOP_LISTENING:
+ if (!mListening) {
+ mListening = true;
+ TileService.this.onStartListening();
+ }
+ break;
+ case MSG_TILE_CLICKED:
+ TileService.this.onClick();
+ break;
+ }
+ }
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6bdf71b..42ede31 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1831,6 +1831,12 @@
<permission android:name="android.permission.STATUS_BAR_SERVICE"
android:protectionLevel="signature" />
+ <!-- Allows an application to bind to third party quick settings tiles.
+ <p>Should only be requested by the System, should be required by
+ QSTileService declarations.-->
+ <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to force a BACK operation on whatever is the
top activity.
<p>Not for use by third-party applications.
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index c36cab8..585f3c7 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -21,10 +21,6 @@
<PreferenceScreen
android:title="@string/quick_settings">
- <Preference
- android:key="qs_tuner"
- android:title="@string/qs_rearrange" />
-
<PreferenceCategory
android:title="@string/experimental">
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 049754e..bb2b8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -77,9 +77,9 @@
private Record mDetailRecord;
private Callback mCallback;
private BrightnessController mBrightnessController;
- private QSTileHost mHost;
+ protected QSTileHost mHost;
- private QSFooter mFooter;
+ protected QSFooter mFooter;
private boolean mGridContentVisible = true;
protected LinearLayout mQsContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 5d928d6..7f45545 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -44,8 +44,8 @@
* state update pass on tile looper.
*/
public abstract class QSTile<TState extends State> implements Listenable {
- protected final String TAG = "QSTile." + getClass().getSimpleName();
- protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
+ protected final String TAG = "Tile." + getClass().getSimpleName();
+ protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
protected final Host mHost;
protected final Context mContext;
@@ -332,7 +332,7 @@
Looper getLooper();
Context getContext();
Collection<QSTile<?>> getTiles();
- void setCallback(Callback callback);
+ void addCallback(Callback callback);
BluetoothController getBluetoothController();
LocationController getLocationController();
RotationLockController getRotationLockController();
@@ -453,9 +453,9 @@
public static class State {
public boolean visible;
public Icon icon;
- public String label;
- public String contentDescription;
- public String dualLabelContentDescription;
+ public CharSequence label;
+ public CharSequence contentDescription;
+ public CharSequence dualLabelContentDescription;
public boolean autoMirrorDrawable = true;
public boolean copyTo(State other) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
new file mode 100644
index 0000000..55f4736
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import android.os.IBinder;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+
+public class QSTileServiceWrapper implements IQSTileService {
+ private static final String TAG = "IQSTileServiceWrapper";
+
+ private final IQSTileService mService;
+
+ public QSTileServiceWrapper(IQSTileService service) {
+ mService = service;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mService.asBinder();
+ }
+
+ @Override
+ public void setQSTile(Tile tile) {
+ try {
+ mService.setQSTile(tile);
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onTileAdded() {
+ try {
+ mService.onTileAdded();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onTileRemoved() {
+ try {
+ mService.onTileRemoved();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onStartListening() {
+ try {
+ mService.onStartListening();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onStopListening() {
+ try {
+ mService.onStopListening();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onClick() {
+ try {
+ mService.onClick();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index cc264a0..f32cfdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -24,22 +25,16 @@
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
-
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.AnimationIcon;
-import com.android.systemui.qs.QSTile.State;
import java.util.Objects;
@@ -227,6 +222,7 @@
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.CENTER_INSIDE);
+ icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
return icon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
new file mode 100644
index 0000000..a4ff685
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.customize;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class BlankCustomTile extends QSTile<QSTile.State> {
+ public static final String PREFIX = "custom(";
+
+ private final ComponentName mComponent;
+
+ private BlankCustomTile(Host host, String action) {
+ super(host);
+ mComponent = ComponentName.unflattenFromString(action);
+ }
+
+ public static QSTile<?> create(Host host, String spec) {
+ if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+ throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+ }
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return new BlankCustomTile(host, action);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ super.handleUserSwitch(newUserId);
+ }
+
+ @Override
+ protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+ }
+
+ @Override
+ protected void handleLongClick() {
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+ state.visible = true;
+ state.icon = new DrawableIcon(info.loadIcon(pm));
+ state.label = info.loadLabel(pm).toString();
+ state.contentDescription = state.label;
+ } catch (Exception e) {
+ state.visible = false;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_INTENT;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index 8866e55..422ae4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -15,16 +15,34 @@
*/
package com.android.systemui.qs.customize;
+import android.app.ActivityManager;
+import android.app.Service;
import android.content.ClipData;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.service.quicksettings.IQSTileService;
+import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.qs.tiles.CustomTile;
import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
* A version of QSPanel that allows tiles to be dragged around rather than
@@ -32,8 +50,14 @@
* and the saving/ordering is handled by the CustomQSTileHost.
*/
public class CustomQSPanel extends QSPanel {
+
+ private static final String TAG = "CustomQSPanel";
- private CustomQSTileHost mCustomHost;
+ private List<String> mSavedTiles;
+ private ArrayList<String> mStash;
+ private List<String> mTiles = new ArrayList<>();
+
+ private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
public CustomQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -41,12 +65,9 @@
.inflate(R.layout.qs_customize_layout, mQsContainer, false);
mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
- }
+ removeView(mFooter.getView());
- @Override
- public void setHost(QSTileHost host) {
- super.setHost(host);
- mCustomHost = (CustomQSTileHost) host;
+ TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
}
@Override
@@ -55,17 +76,16 @@
// No Brightness for you.
super.onTuningChanged(key, "0");
}
- }
-
- public CustomQSTileHost getCustomHost() {
- return mCustomHost;
+ if (QSTileHost.TILES_SETTING.equals(key)) {
+ mSavedTiles = QSTileHost.loadTileSpecs(mContext, newValue);
+ }
}
public void tileSelected(QSTile<?> tile, ClipData currentClip) {
String sourceSpec = getSpec(currentClip);
String destSpec = tile.getTileSpec();
if (!sourceSpec.equals(destSpec)) {
- mCustomHost.moveTo(sourceSpec, destSpec);
+ moveTo(sourceSpec, destSpec);
}
}
@@ -79,4 +99,124 @@
public String getSpec(ClipData data) {
return data.getItemAt(0).getText().toString();
}
+
+ public void setSavedTiles() {
+ setTiles(mSavedTiles);
+ }
+
+ public void saveCurrentTiles() {
+ for (int i = 0; i < mSavedTiles.size(); i++) {
+ String tileSpec = mSavedTiles.get(i);
+ if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+ if (!mTiles.contains(tileSpec)) {
+ mContext.bindServiceAsUser(
+ new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+ new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+ IQSTileService.Stub.asInterface(service));
+ wrapper.onStopListening();
+ wrapper.onTileRemoved();
+ mContext.unbindService(this);
+ }
+ }, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
+ }
+ for (int i = 0; i < mTiles.size(); i++) {
+ String tileSpec = mTiles.get(i);
+ if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+ if (!mSavedTiles.contains(tileSpec)) {
+ mContext.bindServiceAsUser(
+ new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+ new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+ IQSTileService.Stub.asInterface(service));
+ wrapper.onTileAdded();
+ mContext.unbindService(this);
+ }
+ }, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
+ }
+ Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
+ TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
+ }
+
+ public void stashCurrentTiles() {
+ mStash = new ArrayList<>(mTiles);
+ }
+
+ public void unstashTiles() {
+ setTiles(mStash);
+ }
+
+ @Override
+ public void setTiles(Collection<QSTile<?>> tiles) {
+ setTilesInternal();
+ }
+
+ private void setTilesInternal() {
+ for (int i = 0; i < mCurrentTiles.size(); i++) {
+ mCurrentTiles.get(i).destroy();
+ }
+ mCurrentTiles.clear();
+ for (int i = 0; i < mTiles.size(); i++) {
+ if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
+ mCurrentTiles.add(BlankCustomTile.create(mHost, mTiles.get(i)));
+ } else {
+ mCurrentTiles.add(mHost.createTile(mTiles.get(i)));
+ }
+ mCurrentTiles.get(mCurrentTiles.size() - 1).setTileSpec(mTiles.get(i));
+ }
+ super.setTiles(mCurrentTiles);
+ }
+
+ public void addTile(String spec) {
+ mTiles.add(spec);
+ setTilesInternal();
+ }
+
+ public void moveTo(String from, String to) {
+ int fromIndex = mTiles.indexOf(from);
+ if (fromIndex < 0) {
+ Log.e(TAG, "Unknown from tile " + from);
+ return;
+ }
+ int index = mTiles.indexOf(to);
+ if (index < 0) {
+ Log.e(TAG, "Unknown to tile " + to);
+ return;
+ }
+ mTiles.remove(fromIndex);
+ mTiles.add(index, from);
+ setTilesInternal();
+ }
+
+ public void remove(String spec) {
+ if (!mTiles.remove(spec)) {
+ Log.e(TAG, "Unknown remove spec " + spec);
+ }
+ setTilesInternal();
+ }
+
+ public void setTiles(List<String> tiles) {
+ mTiles = new ArrayList<>(tiles);
+ setTilesInternal();
+ }
+
+ public Collection<QSTile<?>> getTiles() {
+ return mCurrentTiles;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
deleted file mode 100644
index 3f85982..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2015 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.customize;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @see CustomQSPanel
- */
-public class CustomQSTileHost extends QSTileHost {
-
- private static final String TAG = "CustomHost";
- private List<String> mTiles;
- private List<String> mSavedTiles;
- private ArrayList<String> mStash;
-
- public CustomQSTileHost(Context context, QSTileHost host) {
- super(context, null, host.getBluetoothController(), host.getLocationController(),
- host.getRotationLockController(), host.getNetworkController(),
- host.getZenModeController(), host.getHotspotController(), host.getCastController(),
- host.getFlashlightController(), host.getUserSwitcherController(),
- host.getUserInfoController(), host.getKeyguardMonitor(),
- new BlankSecurityController(), host.getBatteryController());
- }
-
- @Override
- public QSTile<?> createTile(String tileSpec) {
- QSTile<?> tile = super.createTile(tileSpec);
- tile.setTileSpec(tileSpec);
- return tile;
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- // No Tunings For You.
- if (TILES_SETTING.equals(key)) {
- mSavedTiles = super.loadTileSpecs(newValue);
- }
- }
-
- public void setSavedTiles() {
- setTiles(mSavedTiles);
- }
-
- public void saveCurrentTiles() {
- Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
- TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
- }
-
- public void stashCurrentTiles() {
- mStash = new ArrayList<>(mTiles);
- }
-
- public void unstashTiles() {
- setTiles(mStash);
- }
-
- public void moveTo(String from, String to) {
- int fromIndex = mTiles.indexOf(from);
- if (fromIndex < 0) {
- Log.e(TAG, "Unknown from tile " + from);
- return;
- }
- int index = mTiles.indexOf(to);
- if (index < 0) {
- Log.e(TAG, "Unknown to tile " + to);
- return;
- }
- mTiles.remove(fromIndex);
- mTiles.add(index, from);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void remove(String spec) {
- if (!mTiles.remove(spec)) {
- Log.e(TAG, "Unknown remove spec " + spec);
- }
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void setTiles(List<String> tiles) {
- mTiles = new ArrayList<>(tiles);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- @Override
- protected List<String> loadTileSpecs(String tileList) {
- return mTiles;
- }
-
- public void addTile(String spec) {
- mTiles.add(spec);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void replace(String oldTile, String newTile) {
- if (oldTile.equals(newTile)) {
- return;
- }
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
- + newTile);
- List<String> order = new ArrayList<>(mTileSpecs);
- int index = order.indexOf(oldTile);
- if (index < 0) {
- Log.e(TAG, "Can't find " + oldTile);
- return;
- }
- order.remove(newTile);
- order.add(index, newTile);
- setTiles(order);
- }
-
- /**
- * Blank so that the customizing QS view doesn't show any security messages in the footer.
- */
- private static class BlankSecurityController implements SecurityController {
- @Override
- public boolean hasDeviceOwner() {
- return false;
- }
-
- @Override
- public boolean hasProfileOwner() {
- return false;
- }
-
- @Override
- public String getDeviceOwnerName() {
- return null;
- }
-
- @Override
- public String getProfileOwnerName() {
- return null;
- }
-
- @Override
- public boolean isVpnEnabled() {
- return false;
- }
-
- @Override
- public boolean isVpnRestricted() {
- return false;
- }
-
- @Override
- public String getPrimaryVpnName() {
- return null;
- }
-
- @Override
- public String getProfileVpnName() {
- return null;
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- }
-
- @Override
- public void addCallback(SecurityControllerCallback callback) {
- }
-
- @Override
- public void removeCallback(SecurityControllerCallback callback) {
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 1669278..d0d5b54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -83,8 +83,8 @@
record.tileView.setVisibility(View.VISIBLE);
record.tileView.init(null, null, null);
record.tileView.setOnTouchListener(this);
- if (mCurrentClip != null
- && mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) {
+ if (mCurrentClip != null && mCurrentClip.getItemAt(0)
+ .getText().toString().equals(record.tile.getTileSpec())) {
record.tileView.setAlpha(.3f);
mCurrentView = record.tileView;
}
@@ -180,7 +180,7 @@
case MotionEvent.ACTION_DOWN:
// Stash the current tiles, in case the drop is on info, that we can restore
// the previous state.
- mPanel.getCustomHost().stashCurrentTiles();
+ mPanel.stashCurrentTiles();
mCurrentView = v;
mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index b5a885c..baad370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -22,6 +22,7 @@
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.DragEvent;
@@ -71,11 +72,11 @@
private CustomQSPanel mQsPanel;
private boolean isShown;
- private CustomQSTileHost mHost;
private DropButton mInfoButton;
private DropButton mRemoveButton;
private FloatingActionButton mFab;
private SystemUIDialog mDialog;
+ private QSTileHost mHost;
public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -85,11 +86,11 @@
}
public void setHost(QSTileHost host) {
- mHost = new CustomQSTileHost(mContext, host);
- mHost.setCallback(this);
+ mHost = host;
+ mHost.addCallback(this);
mQsPanel.setTiles(mHost.getTiles());
mQsPanel.setHost(mHost);
- mHost.setSavedTiles();
+ mQsPanel.setSavedTiles();
}
@Override
@@ -129,7 +130,7 @@
public void show(int x, int y) {
isShown = true;
- mHost.setSavedTiles();
+ mQsPanel.setSavedTiles();
mPhoneStatusBar.getStatusBarWindow().addView(this);
mQsPanel.setListening(true);
mClipper.animateCircularClip(x, y, true, this);
@@ -150,7 +151,7 @@
for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) {
tiles.add(tile);
}
- mHost.setTiles(tiles);
+ mQsPanel.setTiles(tiles);
}
private void setDragging(boolean dragging) {
@@ -158,7 +159,8 @@
}
private void save() {
- mHost.saveCurrentTiles();
+ Log.d("CustomQSPanel", "Save!");
+ mQsPanel.saveCurrentTiles();
// TODO: At save button.
hide(0, 0);
}
@@ -167,6 +169,7 @@
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_SAVE:
+ Log.d("CustomQSPanel", "Save...");
save();
break;
case MENU_RESET:
@@ -179,7 +182,7 @@
@Override
public void onTileSelected(String spec) {
if (mDialog != null) {
- mHost.addTile(spec);
+ mQsPanel.addTile(spec);
mDialog.dismiss();
}
}
@@ -203,9 +206,9 @@
public void onDrop(View v, ClipData data) {
if (v == mRemoveButton) {
- mHost.remove(mQsPanel.getSpec(data));
+ mQsPanel.remove(mQsPanel.getSpec(data));
} else if (v == mInfoButton) {
- mHost.unstashTiles();
+ mQsPanel.unstashTiles();
SystemUIDialog dialog = new SystemUIDialog(mContext);
dialog.setTitle(mQsPanel.getSpec(data));
dialog.setPositiveButton(R.string.ok, null);
@@ -220,7 +223,7 @@
android.R.style.Theme_Material_Dialog);
View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
ListView listView = (ListView) view.findViewById(android.R.id.list);
- TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+ TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
adapter.setListener(this);
listView.setDivider(null);
listView.setDividerHeight(0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 579f58d..144b202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -27,6 +27,7 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
+import android.service.quicksettings.TileService;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -82,8 +83,10 @@
continue;
}
if (tileSpecs.contains(spec)) {
+ Log.d(TAG, "Skipping " + spec);
continue;
}
+ Log.d(TAG, "Trying " + spec);
final QSTile<?> tile = host.createTile(spec);
// Bad, bad, very bad.
tile.setListening(true);
@@ -156,7 +159,7 @@
Log.d(TAG, "Added " + mLabel);
}
- private void addTile(String spec, Drawable icon, String label) {
+ private void addTile(String spec, Drawable icon, CharSequence label) {
TileInfo info = new TileInfo();
info.label = label;
info.drawable = icon;
@@ -164,7 +167,7 @@
mTiles.add(info);
}
- private void addTile(String spec, Icon icon, String label, Context context) {
+ private void addTile(String spec, Icon icon, CharSequence label, Context context) {
addTile(spec, icon.getDrawable(context), label);
}
@@ -208,19 +211,17 @@
private static class TileInfo {
private String spec;
private Drawable drawable;
- private String label;
+ private CharSequence label;
}
private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
- // TODO: Become non-prototype and an API.
- private static final String TILE_ACTION = "android.intent.action.QS_TILE";
-
@Override
protected Collection<TileGroup> doInBackground(Void... params) {
HashMap<String, TileGroup> pkgMap = new HashMap<>();
PackageManager pm = mContext.getPackageManager();
// TODO: Handle userness.
- List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+ List<ResolveInfo> services = pm.queryIntentServices(
+ new Intent(TileService.ACTION_QS_TILE), 0);
for (ResolveInfo info : services) {
String packageName = info.serviceInfo.packageName;
ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index fd70d02..7f07ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -133,7 +133,7 @@
R.string.accessibility_quick_settings_bluetooth_off);
}
- String bluetoothName = state.label;
+ CharSequence bluetoothName = state.label;
if (connected) {
bluetoothName = state.dualLabelContentDescription = mContext.getString(
R.string.accessibility_bluetooth_name, state.label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index cf76ed4..b0d885a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -16,36 +16,93 @@
package com.android.systemui.qs.tiles;
+import android.app.ActivityManager;
+import android.app.Service;
import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.statusbar.phone.QSTileHost;
public class CustomTile extends QSTile<QSTile.State> {
public static final String PREFIX = "custom(";
- private final ComponentName mComponent;
+ // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
+ // So instead we have a period of waiting.
+ private static final long UNBIND_DELAY = 30000;
- private CustomTile(Host host, String action) {
+ private final ComponentName mComponent;
+ private final Tile mTile;
+
+ private QSTileServiceWrapper mService;
+ private boolean mListening;
+ private boolean mBound;
+
+ private CustomTile(QSTileHost host, String action) {
super(host);
mComponent = ComponentName.unflattenFromString(action);
+ mTile = new Tile(mComponent, host);
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+ mTile.setIcon(android.graphics.drawable.Icon
+ .createWithResource(mComponent.getPackageName(), info.icon));
+ mTile.setLabel(info.loadLabel(pm));
+ } catch (Exception e) {
+ }
}
- 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 CustomTile(host, action);
+ public ComponentName getComponent() {
+ return mComponent;
+ }
+
+ public Tile getQsTile() {
+ return mTile;
+ }
+
+ public void updateState(Tile tile) {
+ Log.d("TileService", "Setting state " + tile.getLabel());
+ mTile.setIcon(tile.getIcon());
+ mTile.setLabel(tile.getLabel());
+ mTile.setContentDescription(tile.getContentDescription());
}
@Override
public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mHandler.removeCallbacks(mUnbind);
+ if (!mBound) {
+ // TODO: Guarantee re-bind on user-switch.
+ mContext.bindServiceAsUser(new Intent().setComponent(mComponent),
+ mServiceConnection, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ mBound = true;
+ }
+ } else {
+ if (mService!= null) {
+ mService.onStopListening();
+ }
+ mHandler.postDelayed(mUnbind, UNBIND_DELAY);
+ }
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mHandler.removeCallbacks(mUnbind);
+ mUnbind.run();
}
@Override
@@ -60,6 +117,11 @@
@Override
protected void handleClick() {
+ if (mService != null) {
+ mService.onClick();
+ } else {
+ Log.e(TAG, "Click with no service " + getTileSpec());
+ }
MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
}
@@ -69,16 +131,13 @@
@Override
protected void handleUpdateState(State state, Object arg) {
- // TODO: Actual things.
- try {
- PackageManager pm = mContext.getPackageManager();
- ServiceInfo info = pm.getServiceInfo(mComponent, 0);
- state.visible = true;
- state.icon = new DrawableIcon(info.loadIcon(pm));
- state.label = info.loadLabel(pm).toString();
+ state.visible = true;
+ state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+ state.label = mTile.getLabel();
+ if (mTile.getContentDescription() != null) {
+ state.contentDescription = mTile.getContentDescription();
+ } else {
state.contentDescription = state.label;
- } catch (Exception e) {
- state.visible = false;
}
}
@@ -86,4 +145,48 @@
public int getMetricsCategory() {
return MetricsLogger.QS_INTENT;
}
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = new QSTileServiceWrapper(IQSTileService.Stub.asInterface(service));
+ if (mListening) {
+ mService.setQSTile(mTile);
+ mService.onStartListening();
+ } else {
+ mService.onStopListening();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+
+ private final Runnable mUnbind = new Runnable() {
+ @Override
+ public void run() {
+ mContext.unbindService(mServiceConnection);
+ mBound = false;
+ }
+ };
+
+ public static ComponentName getComponentFromSpec(String spec) {
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return ComponentName.unflattenFromString(action);
+ }
+
+ public static QSTile<?> create(QSTileHost host, String spec) {
+ if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+ throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+ }
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return new CustomTile(host, action);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 3763618..7f4442a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -166,7 +166,7 @@
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_wifi,
signalContentDescription);
- String wifiName = state.label;
+ CharSequence wifiName = state.label;
if (state.connected) {
wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
}
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 0cddf1d..51bcf0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -927,7 +927,7 @@
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
- qsh.setCallback(new QSTileHost.Callback() {
+ qsh.addCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
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 96b919e..57c2648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,18 +17,56 @@
package com.android.systemui.statusbar.phone;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.os.Binder;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
+import android.os.RemoteException;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.Tile;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.tiles.*;
-import com.android.systemui.statusbar.policy.*;
+import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.BatteryTile;
+import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.qs.tiles.CastTile;
+import com.android.systemui.qs.tiles.CellularTile;
+import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.tiles.DndTile;
+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.QAirplaneTile;
+import com.android.systemui.qs.tiles.QBluetoothTile;
+import com.android.systemui.qs.tiles.QFlashlightTile;
+import com.android.systemui.qs.tiles.QLockTile;
+import com.android.systemui.qs.tiles.QRotationLockTile;
+import com.android.systemui.qs.tiles.QWifiTile;
+import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.UserTile;
+import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -40,7 +78,7 @@
import java.util.Map;
/** Platform implementation of the quick settings tile host **/
-public class QSTileHost implements QSTile.Host, Tunable {
+public final class QSTileHost extends IQSService.Stub implements QSTile.Host, Tunable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -65,7 +103,7 @@
private final SecurityController mSecurity;
private final BatteryController mBattery;
- private Callback mCallback;
+ private final List<Callback> mCallbacks = new ArrayList<>();
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
@@ -107,8 +145,8 @@
}
@Override
- public void setCallback(Callback callback) {
- mCallback = callback;
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
}
@Override
@@ -209,14 +247,14 @@
public SecurityController getSecurityController() {
return mSecurity;
}
-
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
- final List<String> tileSpecs = loadTileSpecs(newValue);
+ final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
if (tileSpecs.equals(mTileSpecs)) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
@@ -227,11 +265,15 @@
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
if (mTiles.containsKey(tileSpec)) {
- newTiles.put(tileSpec, mTiles.get(tileSpec));
+ QSTile<?> tile = mTiles.get(tileSpec);
+ if (DEBUG) Log.d(TAG, "Adding " + tile);
+ newTiles.put(tileSpec, tile);
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
- newTiles.put(tileSpec, createTile(tileSpec));
+ QSTile<?> tile = createTile(tileSpec);
+ tile.setTileSpec(tileSpec);
+ newTiles.put(tileSpec, tile);
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
}
@@ -241,11 +283,46 @@
mTileSpecs.addAll(tileSpecs);
mTiles.clear();
mTiles.putAll(newTiles);
- if (mCallback != null) {
- mCallback.onTilesChanged();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onTilesChanged();
}
}
+ @Override
+ public void updateQsTile(Tile tile) throws RemoteException {
+ verifyCaller(tile.getComponentName().getPackageName());
+ CustomTile customTile = getTileForComponent(tile.getComponentName());
+ if (customTile != null) {
+ Log.d("TileService", "Got tile update for " + tile.getComponentName());
+ customTile.updateState(tile);
+ customTile.refreshState();
+ }
+ }
+
+ private void verifyCaller(String packageName) {
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(packageName,
+ Binder.getCallingUserHandle().getIdentifier());
+ if (Binder.getCallingUid() != uid) {
+ throw new SecurityException("Component outside caller's uid");
+ }
+ } catch (NameNotFoundException e) {
+ throw new SecurityException(e);
+ }
+ }
+
+ private CustomTile getTileForComponent(ComponentName component) {
+ // TODO: Build map for easier lookup.
+ for (QSTile<?> qsTile : mTiles.values()) {
+ if (qsTile instanceof CustomTile) {
+ if (((CustomTile) qsTile).getComponent().equals(component)) {
+ return (CustomTile) qsTile;
+ }
+ }
+ }
+ return null;
+ }
+
public QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(this, false);
else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
@@ -276,8 +353,8 @@
else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
- protected List<String> loadTileSpecs(String tileList) {
- final Resources res = mContext.getResources();
+ public static List<String> loadTileSpecs(Context context, String tileList) {
+ final Resources res = context.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 662dbd9..cc9f5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -192,7 +192,7 @@
host.getBatteryController());
mHeaderQsPanel.setHost(myHost);
mHeaderQsPanel.setTiles(myHost.getTiles());
- myHost.setCallback(new QSTile.Host.Callback() {
+ myHost.addCallback(new QSTile.Host.Callback() {
@Override
public void onTilesChanged() {
mHeaderQsPanel.setTiles(myHost.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
deleted file mode 100644
index 05e3fd5..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2015 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.tuner;
-
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnDragListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ScrollView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.QSTile.ResourceIcon;
-import com.android.systemui.qs.QSTileBaseView;
-import com.android.systemui.qs.QSTileView;
-import com.android.systemui.qs.tiles.IntentTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class QsTuner extends Fragment implements Callback {
-
- private static final String TAG = "QsTuner";
-
- private static final int MENU_RESET = Menu.FIRST;
-
- private DraggableQsPanel mQsPanel;
- private CustomHost mTileHost;
-
- private FrameLayout mDropTarget;
-
- private ScrollView mScrollRoot;
-
- private FrameLayout mAddTarget;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
- }
-
- public void onResume() {
- super.onResume();
- MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, true);
- }
-
- public void onPause() {
- super.onPause();
- MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, false);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_RESET:
- mTileHost.reset();
- break;
- case android.R.id.home:
- getFragmentManager().popBackStack();
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
-
- mQsPanel = new DraggableQsPanel(getContext());
- mTileHost = new CustomHost(getContext());
- mTileHost.setCallback(this);
- mQsPanel.setTiles(mTileHost.getTiles());
- mQsPanel.setHost(mTileHost);
- mQsPanel.refreshAllTiles();
- ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
-
- mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
- setupDropTarget();
- mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
- setupAddTarget();
- return mScrollRoot;
- }
-
- @Override
- public void onDestroyView() {
- mTileHost.destroy();
- super.onDestroyView();
- }
-
- private void setupDropTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_delete);
- state.label = getString(com.android.internal.R.string.delete);
- tileView.onStateChanged(state);
- mDropTarget.addView(tileView);
- mDropTarget.setVisibility(View.GONE);
- new DragHelper(tileView, new DropListener() {
- @Override
- public void onDrop(String sourceText) {
- mTileHost.remove(sourceText);
- }
- });
- }
-
- private void setupAddTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
- state.label = getString(R.string.add_tile);
- tileView.onStateChanged(state);
- mAddTarget.addView(tileView);
- tileView.setClickable(true);
- tileView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mTileHost.showAddDialog();
- }
- });
- }
-
- public void onStartDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.VISIBLE);
- mAddTarget.setVisibility(View.GONE);
- }
- });
- }
-
- public void stopDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.GONE);
- mAddTarget.setVisibility(View.VISIBLE);
- }
- });
- }
-
- @Override
- public void onTilesChanged() {
- mQsPanel.setTiles(mTileHost.getTiles());
- }
-
- private static int getLabelResource(String spec) {
- if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
- else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
- else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
- else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
- else if (spec.equals("airplane")) return R.string.airplane_mode;
- else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
- else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
- else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
- else if (spec.equals("location")) return R.string.quick_settings_location_label;
- else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
- else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
- return 0;
- }
-
- private static class CustomHost extends QSTileHost {
-
- public CustomHost(Context context) {
- super(context, null, null, null, null, null, null, null, null, null, null,
- null, null, new BlankSecurityController(), null);
- }
-
- @Override
- public QSTile<?> createTile(String tileSpec) {
- return new DraggableTile(this, tileSpec);
- }
-
- public void replace(String oldTile, String newTile) {
- if (oldTile.equals(newTile)) {
- return;
- }
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
- + newTile);
- List<String> order = new ArrayList<>(mTileSpecs);
- int index = order.indexOf(oldTile);
- if (index < 0) {
- Log.e(TAG, "Can't find " + oldTile);
- return;
- }
- order.remove(newTile);
- order.add(index, newTile);
- setTiles(order);
- }
-
- public void remove(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.remove(tile);
- setTiles(tiles);
- }
-
- public void add(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.add(tile);
- setTiles(tiles);
- }
-
- public void reset() {
- Secure.putStringForUser(getContext().getContentResolver(),
- TILES_SETTING, "default", ActivityManager.getCurrentUser());
- }
-
- private void setTiles(List<String> tiles) {
- Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
- TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
- }
-
- public void showAddDialog() {
- List<String> tiles = mTileSpecs;
- int numBroadcast = 0;
- for (int i = 0; i < tiles.size(); i++) {
- if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
- numBroadcast++;
- }
- }
- String[] defaults =
- getContext().getString(R.string.quick_settings_tiles_default).split(",");
- final String[] available = new String[defaults.length + 1
- - (tiles.size() - numBroadcast)];
- final String[] availableTiles = new String[available.length];
- int index = 0;
- for (int i = 0; i < defaults.length; i++) {
- if (tiles.contains(defaults[i])) {
- continue;
- }
- int resource = getLabelResource(defaults[i]);
- if (resource != 0) {
- availableTiles[index] = defaults[i];
- available[index++] = getContext().getString(resource);
- } else {
- availableTiles[index] = defaults[i];
- available[index++] = defaults[i];
- }
- }
- available[index++] = getContext().getString(R.string.broadcast_tile);
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.add_tile)
- .setItems(available, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which < available.length - 1) {
- add(availableTiles[which]);
- } else {
- showBroadcastTileDialog();
- }
- }
- }).show();
- }
-
- public void showBroadcastTileDialog() {
- final EditText editText = new EditText(getContext());
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.broadcast_tile)
- .setView(editText)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- String action = editText.getText().toString();
- if (isValid(action)) {
- add(IntentTile.PREFIX + action + ')');
- }
- }
- }).show();
- }
-
- private boolean isValid(String action) {
- for (int i = 0; i < action.length(); i++) {
- char c = action.charAt(i);
- if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
- return false;
- }
- }
- return true;
- }
-
- private static class BlankSecurityController implements SecurityController {
- @Override
- public boolean hasDeviceOwner() {
- return false;
- }
-
- @Override
- public boolean hasProfileOwner() {
- return false;
- }
-
- @Override
- public String getDeviceOwnerName() {
- return null;
- }
-
- @Override
- public String getProfileOwnerName() {
- return null;
- }
-
- @Override
- public boolean isVpnEnabled() {
- return false;
- }
-
- @Override
- public boolean isVpnRestricted() {
- return false;
- }
-
- @Override
- public String getPrimaryVpnName() {
- return null;
- }
-
- @Override
- public String getProfileVpnName() {
- return null;
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- }
-
- @Override
- public void addCallback(SecurityControllerCallback callback) {
- }
-
- @Override
- public void removeCallback(SecurityControllerCallback callback) {
- }
- }
- }
-
- private static class DraggableTile extends QSTile<QSTile.State>
- implements DropListener {
- private String mSpec;
- private QSTileBaseView mView;
-
- protected DraggableTile(QSTile.Host host, String tileSpec) {
- super(host);
- Log.d(TAG, "Creating tile " + tileSpec);
- mSpec = tileSpec;
- }
-
- @Override
- public QSTileBaseView createTileView(Context context) {
- mView = super.createTileView(context);
- return mView;
- }
-
- @Override
- public int getTileType() {
- return "wifi".equals(mSpec) || "bt".equals(mSpec) ? QSTileView.QS_TYPE_DUAL
- : QSTileView.QS_TYPE_NORMAL;
- }
-
- @Override
- public void setListening(boolean listening) {
- }
-
- @Override
- protected QSTile.State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- protected void handleClick() {
- }
-
- @Override
- protected void handleUpdateState(QSTile.State state, Object arg) {
- state.visible = true;
- state.icon = ResourceIcon.get(getIcon());
- state.label = getLabel();
- }
-
- private String getLabel() {
- int resource = getLabelResource(mSpec);
- if (resource != 0) {
- return mContext.getString(resource);
- }
- if (mSpec.startsWith(IntentTile.PREFIX)) {
- int lastDot = mSpec.lastIndexOf('.');
- if (lastDot >= 0) {
- return mSpec.substring(lastDot + 1, mSpec.length() - 1);
- } else {
- return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1);
- }
- }
- return mSpec;
- }
-
- private int getIcon() {
- if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3;
- else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected;
- else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable;
- else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3;
- else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
- else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on;
- else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
- else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
- else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
- else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
- else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
- return R.drawable.android;
- }
-
- @Override
- public int getMetricsCategory() {
- return 20000;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof DraggableTile) {
- return mSpec.equals(((DraggableTile) o).mSpec);
- }
- return false;
- }
-
- @Override
- public void onDrop(String sourceText) {
- ((CustomHost) mHost).replace(mSpec, sourceText);
- }
-
- }
-
- private class DragHelper implements OnDragListener {
-
- private final View mView;
- private final DropListener mListener;
-
- public DragHelper(View view, DropListener dropListener) {
- mView = view;
- mListener = dropListener;
- mView.setOnDragListener(this);
- }
-
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_ENTERED:
- mView.setBackgroundColor(0x77ffffff);
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- stopDrag();
- case DragEvent.ACTION_DRAG_EXITED:
- mView.setBackgroundColor(0x0);
- break;
- case DragEvent.ACTION_DROP:
- stopDrag();
- String text = event.getClipData().getItemAt(0).getText().toString();
- mListener.onDrop(text);
- break;
- }
- return true;
- }
-
- }
-
- public interface DropListener {
- void onDrop(String sourceText);
- }
-
- private class DraggableQsPanel extends QSPanel implements OnTouchListener {
- public DraggableQsPanel(Context context) {
- super(context);
- mBrightnessView.setVisibility(View.GONE);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- for (TileRecord r : mRecords) {
- new DragHelper(r.tileView, (DraggableTile) r.tile);
- r.tileView.setTag(r.tile);
- r.tileView.setOnTouchListener(this);
-
- for (int i = 0; i < r.tileView.getChildCount(); i++) {
- r.tileView.getChildAt(i).setClickable(false);
- }
- }
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
- ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
- v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
- onStartDrag();
- return true;
- }
- return false;
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index dc7c967..b620b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -59,8 +59,6 @@
private SwitchPreference mBatteryPct;
- private Preference mQsTuner;
-
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -68,17 +66,6 @@
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
setHasOptionsMenu(true);
- mQsTuner = findPreference(KEY_QS_TUNER);
- mQsTuner.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
- ft.addToBackStack(null);
- ft.commit();
- return true;
- }
- });
findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -96,13 +83,6 @@
new TunerWarningFragment().show(getFragmentManager(), WARNING_TAG);
}
}
- TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_THE_NEW_QS);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- TunerService.get(getContext()).removeTunable(mQsPaging);
}
@Override
@@ -175,14 +155,6 @@
}
};
- private final Tunable mQsPaging = new Tunable() {
- @Override
- public void onTuningChanged(String key, String newValue) {
- // Only enable QS rearranging when paging is off, because its very broken.
- mQsTuner.setEnabled(newValue == null || Integer.parseInt(newValue) == 0);
- }
- };
-
public static class TunerWarningFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {