Merge "Round out the QS API"

This commit is contained in:
Jason Monk
2016-01-13 19:07:33 +00:00
committed by Android (Google) Code Review
14 changed files with 273 additions and 3 deletions

View File

@@ -33726,17 +33726,24 @@ package android.service.quicksettings {
method public java.lang.CharSequence getContentDescription();
method public android.graphics.drawable.Icon getIcon();
method public java.lang.CharSequence getLabel();
method public int getState();
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 setState(int);
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;
field public static final int STATE_ACTIVE = 2; // 0x2
field public static final int STATE_INACTIVE = 1; // 0x1
field public static final int STATE_UNAVAILABLE = 0; // 0x0
}
public class TileService extends android.app.Service {
ctor public TileService();
method public final android.service.quicksettings.Tile getQsTile();
method public final boolean isLocked();
method public final boolean isSecure();
method public android.os.IBinder onBind(android.content.Intent);
method public void onClick();
method public void onStartListening();
@@ -33745,6 +33752,8 @@ package android.service.quicksettings {
method public void onTileRemoved();
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
method public final void startActivityAndCollapse(android.content.Intent);
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final int TILE_MODE_ACTIVE = 2; // 0x2
field public static final int TILE_MODE_PASSIVE = 1; // 0x1

View File

@@ -35896,17 +35896,24 @@ package android.service.quicksettings {
method public java.lang.CharSequence getContentDescription();
method public android.graphics.drawable.Icon getIcon();
method public java.lang.CharSequence getLabel();
method public int getState();
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 setState(int);
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;
field public static final int STATE_ACTIVE = 2; // 0x2
field public static final int STATE_INACTIVE = 1; // 0x1
field public static final int STATE_UNAVAILABLE = 0; // 0x0
}
public class TileService extends android.app.Service {
ctor public TileService();
method public final android.service.quicksettings.Tile getQsTile();
method public final boolean isLocked();
method public final boolean isSecure();
method public android.os.IBinder onBind(android.content.Intent);
method public void onClick();
method public void onStartListening();
@@ -35916,6 +35923,8 @@ package android.service.quicksettings {
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void setStatusIcon(android.graphics.drawable.Icon, java.lang.String);
method public final void showDialog(android.app.Dialog);
method public final void startActivityAndCollapse(android.content.Intent);
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final int TILE_MODE_ACTIVE = 2; // 0x2
field public static final int TILE_MODE_PASSIVE = 1; // 0x1

View File

@@ -33740,17 +33740,24 @@ package android.service.quicksettings {
method public java.lang.CharSequence getContentDescription();
method public android.graphics.drawable.Icon getIcon();
method public java.lang.CharSequence getLabel();
method public int getState();
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 setState(int);
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;
field public static final int STATE_ACTIVE = 2; // 0x2
field public static final int STATE_INACTIVE = 1; // 0x1
field public static final int STATE_UNAVAILABLE = 0; // 0x0
}
public class TileService extends android.app.Service {
ctor public TileService();
method public final android.service.quicksettings.Tile getQsTile();
method public final boolean isLocked();
method public final boolean isSecure();
method public android.os.IBinder onBind(android.content.Intent);
method public void onClick();
method public void onStartListening();
@@ -33759,6 +33766,8 @@ package android.service.quicksettings {
method public void onTileRemoved();
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
method public final void startActivityAndCollapse(android.content.Intent);
method public final void unlockAndRun(java.lang.Runnable);
field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final int TILE_MODE_ACTIVE = 2; // 0x2
field public static final int TILE_MODE_PASSIVE = 1; // 0x1

View File

@@ -27,5 +27,9 @@ interface IQSService {
void updateStatusIcon(in Tile tile, in Icon icon,
String contentDescription);
void onShowDialog(in Tile tile);
void onStartActivity(in Tile tile);
void setTileMode(in ComponentName component, int mode);
boolean isLocked();
boolean isSecure();
void startUnlockAndRun(in Tile tile);
}

View File

@@ -29,4 +29,5 @@ oneway interface IQSTileService {
void onStartListening();
void onStopListening();
void onClick(IBinder wtoken);
void onUnlockComplete();
}

View File

@@ -36,10 +36,37 @@ public final class Tile implements Parcelable {
private static final String TAG = "Tile";
/**
* This is the default state of any tile, until updated by the {@link TileService}.
* <p>
* An unavailable state indicates that for some reason this tile is not currently
* available to the user for some reason, and will have no click action. The tile's
* icon will be tinted differently to reflect this state.
*/
public static final int STATE_UNAVAILABLE = 0;
/**
* This represents a tile that is currently in a disabled state but is still interactable.
*
* A disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
* bluetooth disabled), but is still interactable by the user to modify this state. Tiles
* that have boolean states should use this to represent one of their states. The tile's
* icon will be tinted differently to reflect this state, but still be distinct from unavailable.
*/
public static final int STATE_INACTIVE = 1;
/**
* This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is on,
* cast is casting).
*/
public static final int STATE_ACTIVE = 2;
private ComponentName mComponentName;
private Icon mIcon;
private CharSequence mLabel;
private CharSequence mContentDescription;
// Default to active until clients of the new API can update.
private int mState = STATE_ACTIVE;
private IQSService mService;
@@ -78,6 +105,29 @@ public final class Tile implements Parcelable {
return mService;
}
/**
* The current state of the tile.
*
* @see #STATE_UNAVAILABLE
* @see #STATE_INACTIVE
* @see #STATE_ACTIVE
*/
public int getState() {
return mState;
}
/**
* Sets the current state for the tile.
*
* Does not take effect until {@link #updateTile()} is called.
*
* @param state One of {@link #STATE_UNAVAILABLE}, {@link #STATE_INACTIVE},
* {@link #STATE_ACTIVE}
*/
public void setState(int state) {
mState = state;
}
/**
* Gets the current icon for the tile.
*/
@@ -165,6 +215,7 @@ public final class Tile implements Parcelable {
} else {
dest.writeByte((byte) 0);
}
dest.writeInt(mState);
TextUtils.writeToParcel(mLabel, dest, flags);
TextUtils.writeToParcel(mContentDescription, dest, flags);
}
@@ -180,6 +231,7 @@ public final class Tile implements Parcelable {
} else {
mIcon = null;
}
mState = source.readInt();
mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
}

View File

@@ -118,6 +118,7 @@ public class TileService extends Service {
private Tile mTile;
private IBinder mToken;
private IQSService mService;
private Runnable mUnlockRunnable;
@Override
public void onDestroy() {
@@ -199,6 +200,8 @@ public class TileService extends Service {
* This will collapse the Quick Settings panel and show the dialog.
*
* @param dialog Dialog to show.
*
* @see #isLocked()
*/
public final void showDialog(Dialog dialog) {
dialog.getWindow().getAttributes().token = mToken;
@@ -210,6 +213,67 @@ public class TileService extends Service {
}
}
/**
* Prompts the user to unlock the device before executing the Runnable.
* <p>
* The user will be prompted for their current security method if applicable
* and if successful, runnable will be executed. The Runnable will not be
* executed if the user fails to unlock the device or cancels the operation.
*/
public final void unlockAndRun(Runnable runnable) {
mUnlockRunnable = runnable;
try {
mService.startUnlockAndRun(mTile);
} catch (RemoteException e) {
}
}
/**
* Checks if the device is in a secure state.
*
* TileServices should detect when the device is secure and change their behavior
* accordingly.
*
* @return true if the device is secure.
*/
public final boolean isSecure() {
try {
return mService.isSecure();
} catch (RemoteException e) {
return true;
}
}
/**
* Checks if the lock screen is showing.
*
* When a device is locked, then {@link #showDialog} will not present a dialog, as it will
* be under the lock screen. If the behavior of the Tile is safe to do while locked,
* then the user should use {@link #startActivity} to launch an activity on top of the lock
* screen, otherwise the tile should use {@link #unlockAndRun(Runnable)} to give the
* user their security challenge.
*
* @return true if the device is locked.
*/
public final boolean isLocked() {
try {
return mService.isLocked();
} catch (RemoteException e) {
return true;
}
}
/**
* Start an activity while collapsing the panel.
*/
public final void startActivityAndCollapse(Intent intent) {
startActivity(intent);
try {
mService.onStartActivity(mTile);
} catch (RemoteException e) {
}
}
/**
* Gets the {@link Tile} for this service.
* <p/>
@@ -258,6 +322,11 @@ public class TileService extends Service {
public void onClick(IBinder wtoken) throws RemoteException {
mHandler.obtainMessage(H.MSG_TILE_CLICKED, wtoken).sendToTarget();
}
@Override
public void onUnlockComplete() throws RemoteException{
mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE);
}
};
}
@@ -269,6 +338,7 @@ public class TileService extends Service {
private static final int MSG_TILE_REMOVED = 5;
private static final int MSG_TILE_CLICKED = 6;
private static final int MSG_SET_SERVICE = 7;
private static final int MSG_UNLOCK_COMPLETE = 8;
public H(Looper looper) {
super(looper);
@@ -323,6 +393,11 @@ public class TileService extends Service {
mToken = (IBinder) msg.obj;
TileService.this.onClick();
break;
case MSG_UNLOCK_COMPLETE:
if (mUnlockRunnable != null) {
mUnlockRunnable.run();
}
break;
}
}
}

View File

@@ -147,4 +147,8 @@
<color name="default_remote_input_background">@*android:color/notification_default_color</color>
<color name="remote_input_hint">#4dffffff</color>
<color name="qs_tile_tint_unavailable">#40ffffff</color>
<color name="qs_tile_tint_inactive">#4dffffff</color>
<color name="qs_tile_tint_active">#ffffffff</color>
</resources>

View File

@@ -19,8 +19,9 @@ package com.android.systemui.qs.customize;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
public class BlankCustomTile extends QSTile<QSTile.State> {
@@ -72,7 +73,9 @@ public class BlankCustomTile extends QSTile<QSTile.State> {
try {
PackageManager pm = mContext.getPackageManager();
ServiceInfo info = pm.getServiceInfo(mComponent, 0);
state.icon = new DrawableIcon(info.loadIcon(pm));
Drawable drawable = info.loadIcon(pm);
drawable.setTint(mContext.getColor(R.color.qs_tile_tint_active));
state.icon = new DrawableIcon(drawable);
state.label = info.loadLabel(pm).toString();
state.contentDescription = state.label;
} catch (Exception e) {

View File

@@ -25,11 +25,14 @@ import android.os.RemoteException;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -88,6 +91,7 @@ public class CustomTile extends QSTile<QSTile.State> {
mTile.setIcon(tile.getIcon());
mTile.setLabel(tile.getLabel());
mTile.setContentDescription(tile.getContentDescription());
mTile.setState(tile.getState());
}
public void onDialogShown() {
@@ -147,6 +151,9 @@ public class CustomTile extends QSTile<QSTile.State> {
@Override
protected void handleClick() {
if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
return;
}
try {
if (DEBUG) Log.d(TAG, "Adding token");
mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
@@ -172,9 +179,15 @@ public class CustomTile extends QSTile<QSTile.State> {
@Override
protected void handleUpdateState(State state, Object arg) {
Drawable drawable = mTile.getIcon().loadDrawable(mContext);
drawable.setTint(mContext.getColor(android.R.color.white));
int color = mContext.getColor(getColor(mTile.getState()));
drawable.setTint(color);
state.icon = new DrawableIcon(drawable);
state.label = mTile.getLabel();
if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
state.label = new SpannableStringBuilder().append(state.label,
new ForegroundColorSpan(color),
SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
}
if (mTile.getContentDescription() != null) {
state.contentDescription = mTile.getContentDescription();
} else {
@@ -187,6 +200,30 @@ public class CustomTile extends QSTile<QSTile.State> {
return MetricsLogger.QS_CUSTOM;
}
public void startUnlockAndRun() {
mHost.startRunnableDismissingKeyguard(new Runnable() {
@Override
public void run() {
try {
mService.onUnlockComplete();
} catch (RemoteException e) {
}
}
});
}
private static int getColor(int state) {
switch (state) {
case Tile.STATE_UNAVAILABLE:
return R.color.qs_tile_tint_unavailable;
case Tile.STATE_INACTIVE:
return R.color.qs_tile_tint_inactive;
case Tile.STATE_ACTIVE:
return R.color.qs_tile_tint_active;
}
return 0;
}
public static ComponentName getComponentFromSpec(String spec) {
final String action = spec.substring(PREFIX.length(), spec.length() - 1);
if (action.isEmpty()) {

View File

@@ -104,4 +104,14 @@ public class QSTileServiceWrapper {
return false;
}
}
public boolean onUnlockComplete() {
try {
mService.onUnlockComplete();
return true;
} catch (Exception e) {
Log.d(TAG, "Caught exception from TileService", e);
return false;
}
}
}

View File

@@ -55,6 +55,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
private static final int MSG_ON_ADDED = 0;
private static final int MSG_ON_REMOVED = 1;
private static final int MSG_ON_CLICK = 2;
private static final int MSG_ON_UNLOCK_COMPLETE = 3;
// Bind retry control.
private static final int MAX_BIND_RETRIES = 5;
@@ -174,6 +175,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements
onClick(mClickBinder);
}
}
if (queue.contains(MSG_ON_UNLOCK_COMPLETE)) {
if (DEBUG) Log.d(TAG, "Handling pending onUnlockComplete");
if (!mListening) {
Log.w(TAG, "Managed to get unlock on non-listening state...");
// Skipping unlock since lost click privileges.
} else {
onUnlockComplete();
}
}
if (queue.contains(MSG_ON_REMOVED)) {
if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
if (mListening) {
@@ -347,6 +357,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements
}
}
@Override
public void onUnlockComplete() {
if (DEBUG) Log.d(TAG, "onUnlockComplete");
if (mWrapper == null || !mWrapper.onUnlockComplete()) {
queueMessage(MSG_ON_UNLOCK_COMPLETE);
handleDeath();
}
}
@Override
public IBinder asBinder() {
return mWrapper != null ? mWrapper.asBinder() : null;

View File

@@ -36,6 +36,7 @@ import android.util.Log;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import java.util.ArrayList;
import java.util.Collections;
@@ -198,6 +199,16 @@ public class TileServices extends IQSService.Stub {
}
}
@Override
public void onStartActivity(Tile tile) {
ComponentName componentName = tile.getComponentName();
verifyCaller(componentName.getPackageName());
CustomTile customTile = getTileForComponent(componentName);
if (customTile != null) {
mHost.collapsePanels();
}
}
@Override
public void updateStatusIcon(Tile tile, Icon icon, String contentDescription) {
final ComponentName componentName = tile.getComponentName();
@@ -228,6 +239,28 @@ public class TileServices extends IQSService.Stub {
}
}
@Override
public void startUnlockAndRun(Tile tile) {
ComponentName componentName = tile.getComponentName();
verifyCaller(componentName.getPackageName());
CustomTile customTile = getTileForComponent(componentName);
if (customTile != null) {
customTile.startUnlockAndRun();
}
}
@Override
public boolean isLocked() {
KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
return keyguardMonitor.isShowing();
}
@Override
public boolean isSecure() {
KeyguardMonitor keyguardMonitor = mHost.getKeyguardMonitor();
return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
}
private CustomTile getTileForComponent(ComponentName component) {
synchronized (mServices) {
return mTiles.get(component);

View File

@@ -283,6 +283,11 @@ public class TileLifecycleManagerTests extends AndroidTestCase {
public void onClick(IBinder iBinder) throws RemoteException {
sendCallback("onClick");
}
@Override
public void onUnlockComplete() throws RemoteException {
sendCallback("onUnlockComplete");
}
};
}