OSDN Git Service

Add Quick Settings API
authorJason Monk <jmonk@google.com>
Fri, 6 Nov 2015 20:47:26 +0000 (15:47 -0500)
committerJason Monk <jmonk@google.com>
Thu, 19 Nov 2015 19:57:11 +0000 (14:57 -0500)
It is a little bit limited right now, but it contains
the lifecycle of a tile getting added/removed, and
listening/not listening and clicks.

SysUI side will need some cleanup later on.

Change-Id: I4db803c8a271f8bf44f2ef710517969a84a95cf0

28 files changed:
Android.mk
api/current.txt
api/system-current.txt
core/java/android/service/quicksettings/IQSService.aidl [new file with mode: 0644]
core/java/android/service/quicksettings/IQSTileService.aidl [new file with mode: 0644]
core/java/android/service/quicksettings/Tile.aidl [new file with mode: 0644]
core/java/android/service/quicksettings/Tile.java [new file with mode: 0644]
core/java/android/service/quicksettings/TileService.java [new file with mode: 0644]
core/res/AndroidManifest.xml
packages/SystemUI/res/xml/tuner_prefs.xml
packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
packages/SystemUI/src/com/android/systemui/qs/QSTile.java
packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java [deleted file]
packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java [deleted file]
packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java

index 71bba0f..7d942eb 100644 (file)
@@ -417,6 +417,8 @@ LOCAL_SRC_FILES += \
        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)
@@ -625,6 +627,7 @@ aidl_files := \
        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)
index 1a7139c..7355fa6 100644 (file)
@@ -30,6 +30,7 @@ package android {
     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";
@@ -29043,6 +29044,35 @@ package android.service.notification {
 
 }
 
+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 {
index f278441..b720411 100644 (file)
@@ -43,6 +43,7 @@ package android {
     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";
@@ -31189,6 +31190,35 @@ package android.service.persistentdata {
 
 }
 
+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 (file)
index 0000000..087eb61
--- /dev/null
@@ -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 (file)
index 0000000..6b46bee
--- /dev/null
@@ -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 (file)
index 0000000..0373326
--- /dev/null
@@ -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 (file)
index 0000000..c8ae171
--- /dev/null
@@ -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 (file)
index 0000000..eba4c6f
--- /dev/null
@@ -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;
+            }
+        }
+    }
+}
index 6bdf71b..42ede31 100644 (file)
     <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.
index c36cab8..585f3c7 100644 (file)
     <PreferenceScreen
         android:title="@string/quick_settings">
 
-        <Preference
-            android:key="qs_tuner"
-            android:title="@string/qs_rearrange" />
-
         <PreferenceCategory
             android:title="@string/experimental">
 
index 049754e..bb2b8fc 100644 (file)
@@ -77,9 +77,9 @@ public class QSPanel extends FrameLayout implements Tunable {
     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;
index 5d928d6..7f45545 100644 (file)
@@ -44,8 +44,8 @@ import java.util.Objects;
  * 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 @@ public abstract class QSTile<TState extends State> implements Listenable {
         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 abstract class QSTile<TState extends State> implements Listenable {
     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 (file)
index 0000000..55f4736
--- /dev/null
@@ -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);
+        }
+    }
+}
index cc264a0..f32cfdc 100644 (file)
@@ -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.Typeface;
 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 @@ public class QSTileView extends QSTileBaseView {
         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 (file)
index 0000000..a4ff685
--- /dev/null
@@ -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;
+    }
+}
index 8866e55..422ae4d 100644 (file)
  */
 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 @@ import com.android.systemui.statusbar.phone.QSTileHost;
  * and the saving/ordering is handled by the CustomQSTileHost.
  */
 public class CustomQSPanel extends QSPanel {
+    
+    private static final String TAG = "CustomQSPanel";
+
+    private List<String> mSavedTiles;
+    private ArrayList<String> mStash;
+    private List<String> mTiles = new ArrayList<>();
 
-    private CustomQSTileHost mCustomHost;
+    private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
 
     public CustomQSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -41,12 +65,9 @@ public class CustomQSPanel extends QSPanel {
                 .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 @@ public class CustomQSPanel extends QSPanel {
             // 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 class CustomQSPanel extends QSPanel {
     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 (file)
index 3f85982..0000000
+++ /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) {
-        }
-    }
-}
index 1669278..d0d5b54 100644 (file)
@@ -83,8 +83,8 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, On
         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 @@ public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, On
             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);
index b5a885c..baad370 100644 (file)
@@ -22,6 +22,7 @@ import android.content.DialogInterface;
 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 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     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 class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     }
 
     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 class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
 
     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 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
         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 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     }
 
     private void save() {
-        mHost.saveCurrentTiles();
+        Log.d("CustomQSPanel", "Save!");
+        mQsPanel.saveCurrentTiles();
         // TODO: At save button.
         hide(0, 0);
     }
@@ -167,6 +169,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     public boolean onMenuItemClick(MenuItem item) {
         switch (item.getItemId()) {
             case MENU_SAVE:
+                Log.d("CustomQSPanel", "Save...");
                 save();
                 break;
             case MENU_RESET:
@@ -179,7 +182,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     @Override
     public void onTileSelected(String spec) {
         if (mDialog != null) {
-            mHost.addTile(spec);
+            mQsPanel.addTile(spec);
             mDialog.dismiss();
         }
     }
@@ -203,9 +206,9 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
 
     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 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
                     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);
index 579f58d..144b202 100644 (file)
@@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable;
 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 @@ public class TileAdapter extends BaseAdapter {
                     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 @@ public class TileAdapter extends BaseAdapter {
             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 @@ public class TileAdapter extends BaseAdapter {
             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 @@ public class TileAdapter extends BaseAdapter {
     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);
index fd70d02..7f07ddc 100644 (file)
@@ -133,7 +133,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState>  {
                     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);
index cf76ed4..b0d885a 100644 (file)
 
 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(";
 
+    // 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 final ComponentName mComponent;
+    private final Tile mTile;
+
+    private QSTileServiceWrapper mService;
+    private boolean mListening;
+    private boolean mBound;
 
-    private CustomTile(Host host, String action) {
+    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 @@ public class CustomTile extends QSTile<QSTile.State> {
 
     @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 @@ public class CustomTile extends QSTile<QSTile.State> {
 
     @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 class CustomTile extends QSTile<QSTile.State> {
     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);
+    }
 }
index 3763618..7f4442a 100644 (file)
@@ -166,7 +166,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
         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);
         }
index 0cddf1d..51bcf0c 100644 (file)
@@ -927,7 +927,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
             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());
index 96b919e..57c2648 100644 (file)
 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.List;
 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 @@ public class QSTileHost implements QSTile.Host, Tunable {
     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 @@ public class QSTileHost implements QSTile.Host, Tunable {
     }
 
     @Override
-    public void setCallback(Callback callback) {
-        mCallback = callback;
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
     }
 
     @Override
@@ -209,14 +247,14 @@ public class QSTileHost implements QSTile.Host, Tunable {
     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 @@ public class QSTileHost implements QSTile.Host, Tunable {
         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,9 +283,44 @@ public class QSTileHost implements QSTile.Host, Tunable {
         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) {
@@ -276,8 +353,8 @@ public class QSTileHost implements QSTile.Host, Tunable {
         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);
index 662dbd9..cc9f5c7 100644 (file)
@@ -192,7 +192,7 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
                 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 (file)
index 05e3fd5..0000000
+++ /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;
-        }
-    }
-
-}
index dc7c967..b620b50 100644 (file)
@@ -59,8 +59,6 @@ public class TunerFragment extends PreferenceFragment {
 
     private SwitchPreference mBatteryPct;
 
-    private Preference mQsTuner;
-
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -68,17 +66,6 @@ public class TunerFragment extends PreferenceFragment {
         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 @@ public class TunerFragment extends PreferenceFragment {
                 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 @@ public class TunerFragment extends PreferenceFragment {
         }
     };
 
-    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) {