diff options
author | 2015-11-19 20:55:14 +0000 | |
---|---|---|
committer | 2015-11-19 20:55:14 +0000 | |
commit | ee35d738f46f01ce71ac8bde665d71ac3a35cbb9 (patch) | |
tree | af94d2f305fc081a42d507528ce18190349ef139 | |
parent | c2138c54146806310c3f53aa9b2cb4775669e2ce (diff) | |
parent | bbadff8603ca6922a0ef89338bee5b59d6dcf641 (diff) |
Merge "Add Quick Settings API"
28 files changed, 1104 insertions, 856 deletions
diff --git a/Android.mk b/Android.mk index cc0749c5e139..9d2ca0d50300 100644 --- a/Android.mk +++ b/Android.mk @@ -418,6 +418,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) @@ -626,6 +628,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) diff --git a/api/current.txt b/api/current.txt index 2d59de10faba..8bac46dabe7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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"; @@ -29051,6 +29052,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 { diff --git a/api/system-current.txt b/api/system-current.txt index 4719988011ff..3a4706b48eed 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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"; @@ -31199,6 +31200,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 index 000000000000..087eb61d906d --- /dev/null +++ b/core/java/android/service/quicksettings/IQSService.aidl @@ -0,0 +1,26 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.quicksettings; + +import android.content.ComponentName; +import android.service.quicksettings.Tile; + +/** + * @hide + */ +interface IQSService { + void updateQsTile(in Tile tile); +} diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl new file mode 100644 index 000000000000..6b46bee588b9 --- /dev/null +++ b/core/java/android/service/quicksettings/IQSTileService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.quicksettings; + +import android.service.quicksettings.Tile; +import android.service.quicksettings.IQSService; + +/** + * @hide + */ +oneway interface IQSTileService { + void setQSTile(in Tile tile); + void onTileAdded(); + void onTileRemoved(); + void onStartListening(); + void onStopListening(); + void onClick(); +} diff --git a/core/java/android/service/quicksettings/Tile.aidl b/core/java/android/service/quicksettings/Tile.aidl new file mode 100644 index 000000000000..0373326d42a7 --- /dev/null +++ b/core/java/android/service/quicksettings/Tile.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.quicksettings; + +parcelable Tile; diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java new file mode 100644 index 000000000000..c8ae171c458e --- /dev/null +++ b/core/java/android/service/quicksettings/Tile.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.quicksettings; + +import android.content.ComponentName; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +/** + * A Tile holds the state of a tile that will be displayed + * in Quick Settings. + * + * A tile in Quick Settings exists as an icon with an accompanied label. + * It also may have content description for accessibility usability. + * The style and layout of the tile may change to match a given + * device. + */ +public final class Tile implements Parcelable { + + private static final String TAG = "Tile"; + + private ComponentName mComponentName; + private IQSService mService; + private Icon mIcon; + private CharSequence mLabel; + private CharSequence mContentDescription; + + /** + * @hide + */ + public Tile(Parcel source) { + readFromParcel(source); + } + + /** + * @hide + */ + public Tile(ComponentName componentName, IQSService service) { + mComponentName = componentName; + mService = service; + } + + /** + * @hide + */ + public ComponentName getComponentName() { + return mComponentName; + } + + /** + * Gets the current icon for the tile. + */ + public Icon getIcon() { + return mIcon; + } + + /** + * Sets the current icon for the tile. + * + * This icon is expected to be white on alpha, and may be + * tinted by the system to match it's theme. + * + * Does not take effect until {@link #updateTile()} is called. + * + * @param icon New icon to show. + */ + public void setIcon(Icon icon) { + this.mIcon = icon; + } + + /** + * Gets the current label for the tile. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * Sets the current label for the tile. + * + * Does not take effect until {@link #updateTile()} is called. + * + * @param label New label to show. + */ + public void setLabel(CharSequence label) { + this.mLabel = label; + } + + /** + * Gets the current content description for the tile. + */ + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * Sets the current content description for the tile. + * + * Does not take effect until {@link #updateTile()} is called. + * + * @param contentDescription New content description to use. + */ + public void setContentDescription(CharSequence contentDescription) { + this.mContentDescription = contentDescription; + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Pushes the state of the Tile to Quick Settings to be displayed. + */ + public void updateTile() { + try { + mService.updateQsTile(this); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't update tile"); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mService); + if (mComponentName != null) { + dest.writeByte((byte) 1); + mComponentName.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } + if (mIcon != null) { + dest.writeByte((byte) 1); + mIcon.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } + TextUtils.writeToParcel(mLabel, dest, flags); + TextUtils.writeToParcel(mContentDescription, dest, flags); + } + + private void readFromParcel(Parcel source) { + mService = IQSService.Stub.asInterface(source.readStrongBinder()); + if (source.readByte() != 0) { + mComponentName = ComponentName.CREATOR.createFromParcel(source); + } else { + mComponentName = null; + } + if (source.readByte() != 0) { + mIcon = Icon.CREATOR.createFromParcel(source); + } else { + mIcon = null; + } + mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + } + + public static final Creator<Tile> CREATOR = new Creator<Tile>() { + @Override + public Tile createFromParcel(Parcel source) { + return new Tile(source); + } + + @Override + public Tile[] newArray(int size) { + return new Tile[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java new file mode 100644 index 000000000000..eba4c6f69d59 --- /dev/null +++ b/core/java/android/service/quicksettings/TileService.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.quicksettings; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; + +/** + * A QSTileService provides the user a tile that can be added to Quick Settings. + * Quick Settings is a space provided that allows the user to change settings and + * take quick actions without leaving the context of their current app. + * + * <p>The lifecycle of a QSTileService is different from some other services in + * that it may be unbound during parts of its lifecycle. Any of the following + * lifecycle events can happen indepently in a separate binding/creation of the + * service.</p> + * + * <ul> + * <li>When a tile is added by the user its QSTileService will be bound to and + * {@link #onTileAdded()} will be called.</li> + * + * <li>When a tile should be up to date and listing will be indicated by + * {@link #onStartListening()} and {@link #onStopListening()}.</li> + * + * <li>When the user removes a tile from Quick Settings {@link #onStopListening()} + * will be called.</li> + * </ul> + * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE} + * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE". + * The label and icon for the service will be used as the default label and + * icon for the tile. Here is an example QSTileService declaration.</p> + * <pre class="prettyprint"> + * {@literal + * <service + * android:name=".MyQSTileService" + * android:label="@string/my_default_tile_label" + * android:icon="@drawable/my_default_icon_label" + * android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> + * <intent-filter> + * <action android:name="android.intent.action.QS_TILE" /> + * </intent-filter> + * </service>} + * </pre> + * + * @see Tile Tile for details about the UI of a Quick Settings Tile. + */ +public class TileService extends Service { + + /** + * Action that identifies a Service as being a QSTileService. + */ + public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE"; + + private final H mHandler = new H(Looper.getMainLooper()); + + private boolean mListening = false; + private Tile mTile; + + /** + * Called when the user adds this tile to Quick Settings. + * <p/> + * Note that this is not guaranteed to be called between {@link #onCreate()} + * and {@link #onStartListening()}, it will only be called when the tile is added + * and not on subsequent binds. + */ + public void onTileAdded() { + } + + /** + * Called when the user removes this tile from Quick Settings. + */ + public void onTileRemoved() { + } + + /** + * Called when this tile moves into a listening state. + * <p/> + * When this tile is in a listening state it is expected to keep the + * UI up to date. Any listeners or callbacks needed to keep this tile + * up to date should be registered here and unregistered in {@link #onStopListening()}. + * + * @see #getQsTile() + * @see Tile#updateTile() + */ + public void onStartListening() { + } + + /** + * Called when this tile moves out of the listening state. + */ + public void onStopListening() { + } + + /** + * Called when the user clicks on this tile. + */ + public void onClick() { + } + + /** + * Gets the {@link Tile} for this service. + * <p/> + * This tile may be used to get or set the current state for this + * tile. This tile is only valid for updates between {@link #onStartListening()} + * and {@link #onStopListening()}. + */ + public final Tile getQsTile() { + return mTile; + } + + @Override + public IBinder onBind(Intent intent) { + return new IQSTileService.Stub() { + @Override + public void setQSTile(Tile tile) throws RemoteException { + mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget(); + } + + @Override + public void onTileRemoved() throws RemoteException { + mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED); + } + + @Override + public void onTileAdded() throws RemoteException { + mHandler.sendEmptyMessage(H.MSG_TILE_ADDED); + } + + @Override + public void onStopListening() throws RemoteException { + mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING); + } + + @Override + public void onStartListening() throws RemoteException { + mHandler.sendEmptyMessage(H.MSG_START_LISTENING); + } + + @Override + public void onClick() throws RemoteException { + mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED); + } + }; + } + + private class H extends Handler { + private static final int MSG_SET_TILE = 1; + private static final int MSG_START_LISTENING = 2; + private static final int MSG_STOP_LISTENING = 3; + private static final int MSG_TILE_ADDED = 4; + private static final int MSG_TILE_REMOVED = 5; + private static final int MSG_TILE_CLICKED = 6; + + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_TILE: + mTile = (Tile) msg.obj; + break; + case MSG_TILE_ADDED: + TileService.this.onTileRemoved(); + break; + case MSG_TILE_REMOVED: + TileService.this.onTileAdded(); + break; + case MSG_START_LISTENING: + if (mListening) { + mListening = false; + TileService.this.onStopListening(); + } + break; + case MSG_STOP_LISTENING: + if (!mListening) { + mListening = true; + TileService.this.onStartListening(); + } + break; + case MSG_TILE_CLICKED: + TileService.this.onClick(); + break; + } + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6bdf71bdba6f..42ede31527d0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1831,6 +1831,12 @@ <permission android:name="android.permission.STATUS_BAR_SERVICE" android:protectionLevel="signature" /> + <!-- Allows an application to bind to third party quick settings tiles. + <p>Should only be requested by the System, should be required by + QSTileService declarations.--> + <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to force a BACK operation on whatever is the top activity. <p>Not for use by third-party applications. diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index c36cab8c3c5a..585f3c747a9d 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -21,10 +21,6 @@ <PreferenceScreen android:title="@string/quick_settings"> - <Preference - android:key="qs_tuner" - android:title="@string/qs_rearrange" /> - <PreferenceCategory android:title="@string/experimental"> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 049754ebaaef..bb2b8fc4742d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -77,9 +77,9 @@ 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; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 5d928d6c3ade..7f45545bf30b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -44,8 +44,8 @@ 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 index 000000000000..55f4736dd74a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java @@ -0,0 +1,76 @@ +package com.android.systemui.qs; + +import android.os.IBinder; +import android.service.quicksettings.IQSTileService; +import android.service.quicksettings.Tile; +import android.util.Log; + + +public class QSTileServiceWrapper implements IQSTileService { + private static final String TAG = "IQSTileServiceWrapper"; + + private final IQSTileService mService; + + public QSTileServiceWrapper(IQSTileService service) { + mService = service; + } + + @Override + public IBinder asBinder() { + return mService.asBinder(); + } + + @Override + public void setQSTile(Tile tile) { + try { + mService.setQSTile(tile); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } + + @Override + public void onTileAdded() { + try { + mService.onTileAdded(); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } + + @Override + public void onTileRemoved() { + try { + mService.onTileRemoved(); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } + + @Override + public void onStartListening() { + try { + mService.onStartListening(); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } + + @Override + public void onStopListening() { + try { + mService.onStopListening(); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } + + @Override + public void onClick() { + try { + mService.onClick(); + } catch (Exception e) { + Log.d(TAG, "Caught exception from QSTileService", e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index cc264a00776d..f32cfdc7b417 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -17,6 +17,7 @@ package com.android.systemui.qs; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -24,22 +25,16 @@ import android.graphics.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 index 000000000000..a4ff6852374a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.customize; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.qs.QSTile; + +public class BlankCustomTile extends QSTile<QSTile.State> { + public static final String PREFIX = "custom("; + + private final ComponentName mComponent; + + private BlankCustomTile(Host host, String action) { + super(host); + mComponent = ComponentName.unflattenFromString(action); + } + + public static QSTile<?> create(Host host, String spec) { + if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) { + throw new IllegalArgumentException("Bad custom tile spec: " + spec); + } + final String action = spec.substring(PREFIX.length(), spec.length() - 1); + if (action.isEmpty()) { + throw new IllegalArgumentException("Empty custom tile spec action"); + } + return new BlankCustomTile(host, action); + } + + @Override + public void setListening(boolean listening) { + } + + @Override + protected State newTileState() { + return new State(); + } + + @Override + protected void handleUserSwitch(int newUserId) { + super.handleUserSwitch(newUserId); + } + + @Override + protected void handleClick() { + MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName()); + } + + @Override + protected void handleLongClick() { + } + + @Override + protected void handleUpdateState(State state, Object arg) { + try { + PackageManager pm = mContext.getPackageManager(); + ServiceInfo info = pm.getServiceInfo(mComponent, 0); + state.visible = true; + state.icon = new DrawableIcon(info.loadIcon(pm)); + state.label = info.loadLabel(pm).toString(); + state.contentDescription = state.label; + } catch (Exception e) { + state.visible = false; + } + } + + @Override + public int getMetricsCategory() { + return MetricsLogger.QS_INTENT; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java index 8866e558d6fb..422ae4d31691 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java @@ -15,16 +15,34 @@ */ package com.android.systemui.qs.customize; +import android.app.ActivityManager; +import android.app.Service; import android.content.ClipData; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.UserHandle; +import android.provider.Settings.Secure; +import android.service.quicksettings.IQSTileService; +import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import com.android.systemui.R; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileServiceWrapper; +import com.android.systemui.qs.tiles.CustomTile; import com.android.systemui.statusbar.phone.QSTileHost; +import com.android.systemui.tuner.TunerService; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * A version of QSPanel that allows tiles to be dragged around rather than @@ -32,8 +50,14 @@ 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 index 3f85982a2f92..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.qs.customize; - -import android.app.ActivityManager; -import android.content.Context; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.logging.MetricsLogger; -import com.android.systemui.qs.QSTile; -import com.android.systemui.statusbar.phone.QSTileHost; -import com.android.systemui.statusbar.policy.SecurityController; - -import java.util.ArrayList; -import java.util.List; - -/** - * @see CustomQSPanel - */ -public class CustomQSTileHost extends QSTileHost { - - private static final String TAG = "CustomHost"; - private List<String> mTiles; - private List<String> mSavedTiles; - private ArrayList<String> mStash; - - public CustomQSTileHost(Context context, QSTileHost host) { - super(context, null, host.getBluetoothController(), host.getLocationController(), - host.getRotationLockController(), host.getNetworkController(), - host.getZenModeController(), host.getHotspotController(), host.getCastController(), - host.getFlashlightController(), host.getUserSwitcherController(), - host.getUserInfoController(), host.getKeyguardMonitor(), - new BlankSecurityController(), host.getBatteryController()); - } - - @Override - public QSTile<?> createTile(String tileSpec) { - QSTile<?> tile = super.createTile(tileSpec); - tile.setTileSpec(tileSpec); - return tile; - } - - @Override - public void onTuningChanged(String key, String newValue) { - // No Tunings For You. - if (TILES_SETTING.equals(key)) { - mSavedTiles = super.loadTileSpecs(newValue); - } - } - - public void setSavedTiles() { - setTiles(mSavedTiles); - } - - public void saveCurrentTiles() { - Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING, - TextUtils.join(",", mTiles), ActivityManager.getCurrentUser()); - } - - public void stashCurrentTiles() { - mStash = new ArrayList<>(mTiles); - } - - public void unstashTiles() { - setTiles(mStash); - } - - public void moveTo(String from, String to) { - int fromIndex = mTiles.indexOf(from); - if (fromIndex < 0) { - Log.e(TAG, "Unknown from tile " + from); - return; - } - int index = mTiles.indexOf(to); - if (index < 0) { - Log.e(TAG, "Unknown to tile " + to); - return; - } - mTiles.remove(fromIndex); - mTiles.add(index, from); - super.onTuningChanged(TILES_SETTING, null); - } - - public void remove(String spec) { - if (!mTiles.remove(spec)) { - Log.e(TAG, "Unknown remove spec " + spec); - } - super.onTuningChanged(TILES_SETTING, null); - } - - public void setTiles(List<String> tiles) { - mTiles = new ArrayList<>(tiles); - super.onTuningChanged(TILES_SETTING, null); - } - - @Override - protected List<String> loadTileSpecs(String tileList) { - return mTiles; - } - - public void addTile(String spec) { - mTiles.add(spec); - super.onTuningChanged(TILES_SETTING, null); - } - - public void replace(String oldTile, String newTile) { - if (oldTile.equals(newTile)) { - return; - } - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + "," - + newTile); - List<String> order = new ArrayList<>(mTileSpecs); - int index = order.indexOf(oldTile); - if (index < 0) { - Log.e(TAG, "Can't find " + oldTile); - return; - } - order.remove(newTile); - order.add(index, newTile); - setTiles(order); - } - - /** - * Blank so that the customizing QS view doesn't show any security messages in the footer. - */ - private static class BlankSecurityController implements SecurityController { - @Override - public boolean hasDeviceOwner() { - return false; - } - - @Override - public boolean hasProfileOwner() { - return false; - } - - @Override - public String getDeviceOwnerName() { - return null; - } - - @Override - public String getProfileOwnerName() { - return null; - } - - @Override - public boolean isVpnEnabled() { - return false; - } - - @Override - public boolean isVpnRestricted() { - return false; - } - - @Override - public String getPrimaryVpnName() { - return null; - } - - @Override - public String getProfileVpnName() { - return null; - } - - @Override - public void onUserSwitched(int newUserId) { - } - - @Override - public void addCallback(SecurityControllerCallback callback) { - } - - @Override - public void removeCallback(SecurityControllerCallback callback) { - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java index 166927881969..d0d5b549039f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java @@ -83,8 +83,8 @@ 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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index b5a885c1bbae..baad370ca014 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -22,6 +22,7 @@ import android.content.DialogInterface; 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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 579f58d0bc36..144b202f5248 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -27,6 +27,7 @@ import android.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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index fd70d025459c..7f07ddc009c5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -133,7 +133,7 @@ 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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java index cf76ed4d8852..b0d885a3a49e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java @@ -16,36 +16,93 @@ package com.android.systemui.qs.tiles; +import android.app.ActivityManager; +import android.app.Service; import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.UserHandle; +import android.service.quicksettings.IQSTileService; +import android.service.quicksettings.Tile; +import android.util.Log; import com.android.internal.logging.MetricsLogger; import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileServiceWrapper; +import com.android.systemui.statusbar.phone.QSTileHost; public class CustomTile extends QSTile<QSTile.State> { public static final String PREFIX = "custom("; + // 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); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 3763618bae21..7f4442a397f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -166,7 +166,7 @@ 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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 0cddf1d851ff..51bcf0c7ad1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -927,7 +927,7 @@ 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()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 96b919e9437e..57c26485ce10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -17,18 +17,56 @@ package com.android.systemui.statusbar.phone; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.os.Binder; import android.os.HandlerThread; import android.os.Looper; import android.os.Process; +import android.os.RemoteException; +import android.service.quicksettings.IQSService; +import android.service.quicksettings.Tile; import android.util.Log; import com.android.systemui.R; import com.android.systemui.qs.QSTile; -import com.android.systemui.qs.tiles.*; -import com.android.systemui.statusbar.policy.*; +import com.android.systemui.qs.tiles.AirplaneModeTile; +import com.android.systemui.qs.tiles.BatteryTile; +import com.android.systemui.qs.tiles.BluetoothTile; +import com.android.systemui.qs.tiles.CastTile; +import com.android.systemui.qs.tiles.CellularTile; +import com.android.systemui.qs.tiles.ColorInversionTile; +import com.android.systemui.qs.tiles.CustomTile; +import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.qs.tiles.FlashlightTile; +import com.android.systemui.qs.tiles.HotspotTile; +import com.android.systemui.qs.tiles.IntentTile; +import com.android.systemui.qs.tiles.LocationTile; +import com.android.systemui.qs.tiles.QAirplaneTile; +import com.android.systemui.qs.tiles.QBluetoothTile; +import com.android.systemui.qs.tiles.QFlashlightTile; +import com.android.systemui.qs.tiles.QLockTile; +import com.android.systemui.qs.tiles.QRotationLockTile; +import com.android.systemui.qs.tiles.QWifiTile; +import com.android.systemui.qs.tiles.RotationLockTile; +import com.android.systemui.qs.tiles.UserTile; +import com.android.systemui.qs.tiles.WifiTile; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BluetoothController; +import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.FlashlightController; +import com.android.systemui.statusbar.policy.HotspotController; +import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.LocationController; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.RotationLockController; +import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -40,7 +78,7 @@ import java.util.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); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java index 662dbd909a37..cc9f5c747c2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java @@ -192,7 +192,7 @@ 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 index 05e3fd50a9f5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.tuner; - -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Fragment; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.Log; -import android.view.DragEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnDragListener; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ScrollView; - -import com.android.internal.logging.MetricsLogger; -import com.android.systemui.R; -import com.android.systemui.qs.QSPanel; -import com.android.systemui.qs.QSTile; -import com.android.systemui.qs.QSTile.Host.Callback; -import com.android.systemui.qs.QSTile.ResourceIcon; -import com.android.systemui.qs.QSTileBaseView; -import com.android.systemui.qs.QSTileView; -import com.android.systemui.qs.tiles.IntentTile; -import com.android.systemui.statusbar.phone.QSTileHost; -import com.android.systemui.statusbar.policy.SecurityController; - -import java.util.ArrayList; -import java.util.List; - -public class QsTuner extends Fragment implements Callback { - - private static final String TAG = "QsTuner"; - - private static final int MENU_RESET = Menu.FIRST; - - private DraggableQsPanel mQsPanel; - private CustomHost mTileHost; - - private FrameLayout mDropTarget; - - private ScrollView mScrollRoot; - - private FrameLayout mAddTarget; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset); - } - - public void onResume() { - super.onResume(); - MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, true); - } - - public void onPause() { - super.onPause(); - MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, false); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_RESET: - mTileHost.reset(); - break; - case android.R.id.home: - getFragmentManager().popBackStack(); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false); - - mQsPanel = new DraggableQsPanel(getContext()); - mTileHost = new CustomHost(getContext()); - mTileHost.setCallback(this); - mQsPanel.setTiles(mTileHost.getTiles()); - mQsPanel.setHost(mTileHost); - mQsPanel.refreshAllTiles(); - ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0); - - mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target); - setupDropTarget(); - mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target); - setupAddTarget(); - return mScrollRoot; - } - - @Override - public void onDestroyView() { - mTileHost.destroy(); - super.onDestroyView(); - } - - private void setupDropTarget() { - QSTileView tileView = new QSTileView(getContext()); - QSTile.State state = new QSTile.State(); - state.visible = true; - state.icon = ResourceIcon.get(R.drawable.ic_delete); - state.label = getString(com.android.internal.R.string.delete); - tileView.onStateChanged(state); - mDropTarget.addView(tileView); - mDropTarget.setVisibility(View.GONE); - new DragHelper(tileView, new DropListener() { - @Override - public void onDrop(String sourceText) { - mTileHost.remove(sourceText); - } - }); - } - - private void setupAddTarget() { - QSTileView tileView = new QSTileView(getContext()); - QSTile.State state = new QSTile.State(); - state.visible = true; - state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs); - state.label = getString(R.string.add_tile); - tileView.onStateChanged(state); - mAddTarget.addView(tileView); - tileView.setClickable(true); - tileView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mTileHost.showAddDialog(); - } - }); - } - - public void onStartDrag() { - mDropTarget.post(new Runnable() { - @Override - public void run() { - mDropTarget.setVisibility(View.VISIBLE); - mAddTarget.setVisibility(View.GONE); - } - }); - } - - public void stopDrag() { - mDropTarget.post(new Runnable() { - @Override - public void run() { - mDropTarget.setVisibility(View.GONE); - mAddTarget.setVisibility(View.VISIBLE); - } - }); - } - - @Override - public void onTilesChanged() { - mQsPanel.setTiles(mTileHost.getTiles()); - } - - private static int getLabelResource(String spec) { - if (spec.equals("wifi")) return R.string.quick_settings_wifi_label; - else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label; - else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label; - else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title; - else if (spec.equals("airplane")) return R.string.airplane_mode; - else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label; - else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label; - else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label; - else if (spec.equals("location")) return R.string.quick_settings_location_label; - else if (spec.equals("cast")) return R.string.quick_settings_cast_title; - else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label; - return 0; - } - - private static class CustomHost extends QSTileHost { - - public CustomHost(Context context) { - super(context, null, null, null, null, null, null, null, null, null, null, - null, null, new BlankSecurityController(), null); - } - - @Override - public QSTile<?> createTile(String tileSpec) { - return new DraggableTile(this, tileSpec); - } - - public void replace(String oldTile, String newTile) { - if (oldTile.equals(newTile)) { - return; - } - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + "," - + newTile); - List<String> order = new ArrayList<>(mTileSpecs); - int index = order.indexOf(oldTile); - if (index < 0) { - Log.e(TAG, "Can't find " + oldTile); - return; - } - order.remove(newTile); - order.add(index, newTile); - setTiles(order); - } - - public void remove(String tile) { - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile); - List<String> tiles = new ArrayList<>(mTileSpecs); - tiles.remove(tile); - setTiles(tiles); - } - - public void add(String tile) { - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile); - List<String> tiles = new ArrayList<>(mTileSpecs); - tiles.add(tile); - setTiles(tiles); - } - - public void reset() { - Secure.putStringForUser(getContext().getContentResolver(), - TILES_SETTING, "default", ActivityManager.getCurrentUser()); - } - - private void setTiles(List<String> tiles) { - Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING, - TextUtils.join(",", tiles), ActivityManager.getCurrentUser()); - } - - public void showAddDialog() { - List<String> tiles = mTileSpecs; - int numBroadcast = 0; - for (int i = 0; i < tiles.size(); i++) { - if (tiles.get(i).startsWith(IntentTile.PREFIX)) { - numBroadcast++; - } - } - String[] defaults = - getContext().getString(R.string.quick_settings_tiles_default).split(","); - final String[] available = new String[defaults.length + 1 - - (tiles.size() - numBroadcast)]; - final String[] availableTiles = new String[available.length]; - int index = 0; - for (int i = 0; i < defaults.length; i++) { - if (tiles.contains(defaults[i])) { - continue; - } - int resource = getLabelResource(defaults[i]); - if (resource != 0) { - availableTiles[index] = defaults[i]; - available[index++] = getContext().getString(resource); - } else { - availableTiles[index] = defaults[i]; - available[index++] = defaults[i]; - } - } - available[index++] = getContext().getString(R.string.broadcast_tile); - new AlertDialog.Builder(getContext()) - .setTitle(R.string.add_tile) - .setItems(available, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which < available.length - 1) { - add(availableTiles[which]); - } else { - showBroadcastTileDialog(); - } - } - }).show(); - } - - public void showBroadcastTileDialog() { - final EditText editText = new EditText(getContext()); - new AlertDialog.Builder(getContext()) - .setTitle(R.string.broadcast_tile) - .setView(editText) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String action = editText.getText().toString(); - if (isValid(action)) { - add(IntentTile.PREFIX + action + ')'); - } - } - }).show(); - } - - private boolean isValid(String action) { - for (int i = 0; i < action.length(); i++) { - char c = action.charAt(i); - if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') { - return false; - } - } - return true; - } - - private static class BlankSecurityController implements SecurityController { - @Override - public boolean hasDeviceOwner() { - return false; - } - - @Override - public boolean hasProfileOwner() { - return false; - } - - @Override - public String getDeviceOwnerName() { - return null; - } - - @Override - public String getProfileOwnerName() { - return null; - } - - @Override - public boolean isVpnEnabled() { - return false; - } - - @Override - public boolean isVpnRestricted() { - return false; - } - - @Override - public String getPrimaryVpnName() { - return null; - } - - @Override - public String getProfileVpnName() { - return null; - } - - @Override - public void onUserSwitched(int newUserId) { - } - - @Override - public void addCallback(SecurityControllerCallback callback) { - } - - @Override - public void removeCallback(SecurityControllerCallback callback) { - } - } - } - - private static class DraggableTile extends QSTile<QSTile.State> - implements DropListener { - private String mSpec; - private QSTileBaseView mView; - - protected DraggableTile(QSTile.Host host, String tileSpec) { - super(host); - Log.d(TAG, "Creating tile " + tileSpec); - mSpec = tileSpec; - } - - @Override - public QSTileBaseView createTileView(Context context) { - mView = super.createTileView(context); - return mView; - } - - @Override - public int getTileType() { - return "wifi".equals(mSpec) || "bt".equals(mSpec) ? QSTileView.QS_TYPE_DUAL - : QSTileView.QS_TYPE_NORMAL; - } - - @Override - public void setListening(boolean listening) { - } - - @Override - protected QSTile.State newTileState() { - return new QSTile.State(); - } - - @Override - protected void handleClick() { - } - - @Override - protected void handleUpdateState(QSTile.State state, Object arg) { - state.visible = true; - state.icon = ResourceIcon.get(getIcon()); - state.label = getLabel(); - } - - private String getLabel() { - int resource = getLabelResource(mSpec); - if (resource != 0) { - return mContext.getString(resource); - } - if (mSpec.startsWith(IntentTile.PREFIX)) { - int lastDot = mSpec.lastIndexOf('.'); - if (lastDot >= 0) { - return mSpec.substring(lastDot + 1, mSpec.length() - 1); - } else { - return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1); - } - } - return mSpec; - } - - private int getIcon() { - if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3; - else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected; - else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable; - else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3; - else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable; - else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on; - else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate; - else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable; - else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable; - else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on; - else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable; - return R.drawable.android; - } - - @Override - public int getMetricsCategory() { - return 20000; - } - - @Override - public boolean equals(Object o) { - if (o instanceof DraggableTile) { - return mSpec.equals(((DraggableTile) o).mSpec); - } - return false; - } - - @Override - public void onDrop(String sourceText) { - ((CustomHost) mHost).replace(mSpec, sourceText); - } - - } - - private class DragHelper implements OnDragListener { - - private final View mView; - private final DropListener mListener; - - public DragHelper(View view, DropListener dropListener) { - mView = view; - mListener = dropListener; - mView.setOnDragListener(this); - } - - @Override - public boolean onDrag(View v, DragEvent event) { - switch (event.getAction()) { - case DragEvent.ACTION_DRAG_ENTERED: - mView.setBackgroundColor(0x77ffffff); - break; - case DragEvent.ACTION_DRAG_ENDED: - stopDrag(); - case DragEvent.ACTION_DRAG_EXITED: - mView.setBackgroundColor(0x0); - break; - case DragEvent.ACTION_DROP: - stopDrag(); - String text = event.getClipData().getItemAt(0).getText().toString(); - mListener.onDrop(text); - break; - } - return true; - } - - } - - public interface DropListener { - void onDrop(String sourceText); - } - - private class DraggableQsPanel extends QSPanel implements OnTouchListener { - public DraggableQsPanel(Context context) { - super(context); - mBrightnessView.setVisibility(View.GONE); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - for (TileRecord r : mRecords) { - new DragHelper(r.tileView, (DraggableTile) r.tile); - r.tileView.setTag(r.tile); - r.tileView.setOnTouchListener(this); - - for (int i = 0; i < r.tileView.getChildCount(); i++) { - r.tileView.getChildAt(i).setClickable(false); - } - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec; - ClipData data = ClipData.newPlainText(tileSpec, tileSpec); - v.startDrag(data, new View.DragShadowBuilder(v), null, 0); - onStartDrag(); - return true; - } - return false; - } - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java index dc7c96732f2a..b620b50bfcff 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java @@ -59,8 +59,6 @@ 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) { |