Merge "More QS Tile modes in the API"

This commit is contained in:
Jason Monk
2016-01-06 14:18:57 +00:00
committed by Android (Google) Code Review
14 changed files with 316 additions and 71 deletions

View File

@@ -33634,10 +33634,13 @@ package android.service.quicksettings {
method public void onClick();
method public void onStartListening();
method public void onStopListening();
method public void onTileAdded();
method public int onTileAdded();
method public void onTileRemoved();
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
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

@@ -35802,10 +35802,13 @@ package android.service.quicksettings {
method public void onClick();
method public void onStartListening();
method public void onStopListening();
method public void onTileAdded();
method public int onTileAdded();
method public void onTileRemoved();
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
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

@@ -33637,10 +33637,13 @@ package android.service.quicksettings {
method public void onClick();
method public void onStartListening();
method public void onStopListening();
method public void onTileAdded();
method public int onTileAdded();
method public void onTileRemoved();
method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
method public final void showDialog(android.app.Dialog);
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

@@ -24,4 +24,5 @@ import android.service.quicksettings.Tile;
interface IQSService {
void updateQsTile(in Tile tile);
void onShowDialog(in Tile tile);
void setTileMode(in ComponentName component, int mode);
}

View File

@@ -22,6 +22,7 @@ import android.service.quicksettings.IQSService;
* @hide
*/
oneway interface IQSTileService {
void setQSService(in IQSService service);
void setQSTile(in Tile tile);
void onTileAdded();
void onTileRemoved();

View File

@@ -37,11 +37,12 @@ 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;
private IQSService mService;
/**
* @hide
*/
@@ -52,8 +53,14 @@ public final class Tile implements Parcelable {
/**
* @hide
*/
public Tile(ComponentName componentName, IQSService service) {
public Tile(ComponentName componentName) {
mComponentName = componentName;
}
/**
* @hide
*/
public void setService(IQSService service) {
mService = service;
}
@@ -64,6 +71,13 @@ public final class Tile implements Parcelable {
return mComponentName;
}
/**
* @hide
*/
public IQSService getQsService() {
return mService;
}
/**
* Gets the current icon for the tile.
*/
@@ -137,21 +151,8 @@ public final class Tile implements Parcelable {
}
}
/**
* @hide
* Notifies the IQSService that this tile is showing a dialog.
*/
void onShowDialog() {
try {
mService.onShowDialog(this);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't onShowDialog");
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongInterface(mService);
if (mComponentName != null) {
dest.writeByte((byte) 1);
mComponentName.writeToParcel(dest, flags);
@@ -169,7 +170,6 @@ public final class Tile implements Parcelable {
}
private void readFromParcel(Parcel source) {
mService = IQSService.Stub.asInterface(source.readStrongBinder());
if (source.readByte() != 0) {
mComponentName = ComponentName.CREATOR.createFromParcel(source);
} else {

View File

@@ -15,8 +15,11 @@
*/
package android.service.quicksettings;
import android.Manifest;
import android.app.Dialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -42,7 +45,7 @@ import android.view.WindowManager;
* <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()}
* <li>When the user removes a tile from Quick Settings {@link #onTileRemoved()}
* will be called.</li>
* </ul>
* <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
@@ -71,11 +74,48 @@ public class TileService extends Service {
*/
public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
/**
* The tile mode hasn't been set yet.
* @hide
*/
public static final int TILE_MODE_UNSET = 0;
/**
* Constant to be returned by {@link #onTileAdded}.
* <p>
* Passive mode is the default mode for tiles. The System will tell the tile
* when it is most important to update by putting it in the listening state.
*/
public static final int TILE_MODE_PASSIVE = 1;
/**
* Constant to be returned by {@link #onTileAdded}.
* <p>
* Active mode is for tiles which already listen and keep track of their state in their
* own process. These tiles may request to send an update to the System while their process
* is alive using {@link #requestListeningState}. The System will only bind these tiles
* on its own when a click needs to occur.
*/
public static final int TILE_MODE_ACTIVE = 2;
/**
* Used to notify SysUI that Listening has be requested.
* @hide
*/
public static final String ACTION_REQUEST_LISTENING
= "android.service.quicksettings.action.REQUEST_LISTENING";
/**
* @hide
*/
public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
private final H mHandler = new H(Looper.getMainLooper());
private boolean mListening = false;
private Tile mTile;
private IBinder mToken;
private IQSService mService;
@Override
public void onDestroy() {
@@ -92,8 +132,12 @@ public class TileService extends Service {
* 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.
*
* @see #TILE_MODE_PASSIVE
* @see #TILE_MODE_ACTIVE
*/
public void onTileAdded() {
public int onTileAdded() {
return TILE_MODE_PASSIVE;
}
/**
@@ -138,7 +182,10 @@ public class TileService extends Service {
dialog.getWindow().getAttributes().token = mToken;
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_QS_DIALOG);
dialog.show();
getQsTile().onShowDialog();
try {
mService.onShowDialog(mTile);
} catch (RemoteException e) {
}
}
/**
@@ -155,6 +202,11 @@ public class TileService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new IQSTileService.Stub() {
@Override
public void setQSService(IQSService service) throws RemoteException {
mHandler.obtainMessage(H.MSG_SET_SERVICE, service).sendToTarget();
}
@Override
public void setQSTile(Tile tile) throws RemoteException {
mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
@@ -194,6 +246,7 @@ public class TileService extends Service {
private static final int MSG_TILE_ADDED = 4;
private static final int MSG_TILE_REMOVED = 5;
private static final int MSG_TILE_CLICKED = 6;
private static final int MSG_SET_SERVICE = 7;
public H(Looper looper) {
super(looper);
@@ -202,11 +255,28 @@ public class TileService extends Service {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_SERVICE:
mService = (IQSService) msg.obj;
if (mTile != null) {
mTile.setService(mService);
}
break;
case MSG_SET_TILE:
mTile = (Tile) msg.obj;
if (mService != null && mTile != null) {
mTile.setService(mService);
}
break;
case MSG_TILE_ADDED:
TileService.this.onTileAdded();
int mode = TileService.this.onTileAdded();
if (mService == null) {
return;
}
try {
mService.setTileMode(new ComponentName(TileService.this,
TileService.this.getClass()), mode);
} catch (RemoteException e) {
}
break;
case MSG_TILE_REMOVED:
TileService.this.onTileRemoved();
@@ -230,4 +300,16 @@ public class TileService extends Service {
}
}
}
/**
* Requests that a tile be put in the listening state so it can send an update.
*
* This method is only applicable to tiles that return {@link #TILE_MODE_ACTIVE} from
* {@link #onTileAdded()}, and will do nothing otherwise.
*/
public static final void requestListeningState(Context context, ComponentName component) {
Intent intent = new Intent(ACTION_REQUEST_LISTENING);
intent.putExtra(EXTRA_COMPONENT, component);
context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
}
}

View File

@@ -24,6 +24,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManager;
@@ -59,7 +60,7 @@ public class CustomTile extends QSTile<QSTile.State> {
mComponent = ComponentName.unflattenFromString(action);
mServiceManager = host.getTileServices().getTileWrapper(this);
mService = mServiceManager.getTileService();
mTile = new Tile(mComponent, host.getTileServices());
mTile = new Tile(mComponent);
try {
PackageManager pm = mContext.getPackageManager();
ServiceInfo info = pm.getServiceInfo(mComponent, 0);
@@ -68,6 +69,11 @@ public class CustomTile extends QSTile<QSTile.State> {
mTile.setLabel(info.loadLabel(pm));
} catch (Exception e) {
}
try {
mService.setQSTile(mTile);
} catch (RemoteException e) {
// Called through wrapper, won't happen here.
}
}
public ComponentName getComponent() {
@@ -94,9 +100,10 @@ public class CustomTile extends QSTile<QSTile.State> {
mListening = listening;
try {
if (listening) {
mServiceManager.setBindRequested(true);
mService.setQSTile(mTile);
mService.onStartListening();
if (mServiceManager.getType() == TileService.TILE_MODE_PASSIVE) {
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
} else {
mService.onStopListening();
if (mIsTokenGranted && !mIsShowingDialog) {
@@ -139,20 +146,20 @@ public class CustomTile extends QSTile<QSTile.State> {
@Override
protected void handleClick() {
if (mService != null) {
try {
if (DEBUG) Log.d(TAG, "Adding token");
mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
mIsTokenGranted = true;
} catch (RemoteException e) {
try {
if (DEBUG) Log.d(TAG, "Adding token");
mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
mIsTokenGranted = true;
} catch (RemoteException e) {
}
try {
if (mServiceManager.getType() == TileService.TILE_MODE_ACTIVE) {
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
try {
mService.onClick(mToken);
} catch (RemoteException e) {
// Called through wrapper, won't happen here.
}
} else {
Log.e(TAG, "Click with no service " + getTileSpec());
mService.onClick(mToken);
} catch (RemoteException e) {
// Called through wrapper, won't happen here.
}
MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.qs.external;
import android.os.IBinder;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.util.Log;
@@ -93,4 +94,14 @@ public class QSTileServiceWrapper {
return false;
}
}
public boolean setQSService(IQSService service) {
try {
mService.setQSService(service);
return true;
} catch (Exception e) {
Log.d(TAG, "Caught exception from TileService", e);
return false;
}
}
}

View File

@@ -29,6 +29,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.support.annotation.VisibleForTesting;
@@ -74,6 +75,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
private boolean mBound;
@VisibleForTesting
boolean mReceiverRegistered;
private IQSService mService;
public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) {
mContext = context;
@@ -82,6 +84,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements
mUser = user;
}
public ComponentName getComponent() {
return mIntent.getComponent();
}
public boolean hasPendingClick() {
synchronized (mQueuedMessages) {
return mQueuedMessages.contains(MSG_ON_CLICK);
@@ -108,6 +114,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent);
// Give it another chance next time it needs to be bound, out of kindness.
mBindTryCount = 0;
mWrapper = null;
mContext.unbindService(this);
}
}
@@ -122,6 +129,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements
service.linkToDeath(this, 0);
} catch (RemoteException e) {
}
setQSService(mService);
setQSTile(mTile);
handlePendingMessages();
}
@@ -145,7 +154,6 @@ public class TileLifecycleManager extends BroadcastReceiver implements
}
if (mListening) {
if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
setQSTile(mTile);
onStartListening();
}
if (queue.contains(MSG_ON_CLICK)) {
@@ -272,6 +280,14 @@ public class TileLifecycleManager extends BroadcastReceiver implements
}
}
@Override
public void setQSService(IQSService service) {
mService = service;
if (mWrapper == null || !mWrapper.setQSService(service)) {
handleDeath();
}
}
@Override
public void onTileAdded() {
if (DEBUG) Log.d(TAG, "onTileAdded");

View File

@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.Handler;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -38,6 +39,9 @@ public class TileServiceManager {
private static final String TAG = "TileServiceManager";
@VisibleForTesting
static final String PREFS_FILE = "CustomTileModes";
private final TileServices mServices;
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
@@ -47,6 +51,7 @@ public class TileServiceManager {
private int mPriority;
private boolean mJustBound;
private long mLastUpdate;
private int mType;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component) {
this(tileServices, handler, new TileLifecycleManager(handler,
@@ -60,6 +65,25 @@ public class TileServiceManager {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
mType = tileServices.getContext().getSharedPreferences(PREFS_FILE, 0)
.getInt(tileLifecycleManager.getComponent().flattenToString(),
TileService.TILE_MODE_UNSET);
mStateManager.setQSService(tileServices);
if (mType == TileService.TILE_MODE_UNSET) {
bindService();
mStateManager.onTileAdded();
}
}
public int getType() {
return mType;
}
public void setType(int type) {
mServices.getContext().getSharedPreferences(PREFS_FILE, 0).edit()
.putInt(mStateManager.getComponent().flattenToString(), type).commit();
mType = type;
mServices.recalculateBindAllowance();
}
public IQSTileService getTileService() {
@@ -70,17 +94,22 @@ public class TileServiceManager {
if (mBindRequested == bindRequested) return;
mBindRequested = bindRequested;
if (mBindAllowed && mBindRequested && !mBound) {
mHandler.removeCallbacks(mUnbind);
bindService();
} else {
mServices.recalculateBindAllowance();
}
if (mBound && !mBindRequested) {
// TODO: Schedule unbind.
mHandler.postDelayed(mUnbind, UNBIND_DELAY);
}
}
public void setLastUpdate(long lastUpdate) {
mLastUpdate = lastUpdate;
if (mBound && mType == TileService.TILE_MODE_ACTIVE) {
mStateManager.onStopListening();
setBindRequested(false);
}
mServices.recalculateBindAllowance();
}
@@ -148,6 +177,15 @@ public class TileServiceManager {
return mPriority;
}
private final Runnable mUnbind = new Runnable() {
@Override
public void run() {
if (mBound && !mBindRequested) {
unbindService();
}
}
};
@VisibleForTesting
final Runnable mJustBoundOver = new Runnable() {
@Override

View File

@@ -15,15 +15,21 @@
*/
package com.android.systemui.qs.external;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.ArrayMap;
import android.util.Log;
import com.android.systemui.statusbar.phone.QSTileHost;
import java.util.ArrayList;
@@ -48,6 +54,8 @@ public class TileServices extends IQSService.Stub {
public TileServices(QSTileHost host, Looper looper) {
mHost = host;
mContext = mHost.getContext();
mContext.registerReceiver(mRequestListeningReceiver,
new IntentFilter(TileService.ACTION_REQUEST_LISTENING));
mHandler = new Handler(looper);
}
@@ -121,12 +129,44 @@ public class TileServices extends IQSService.Stub {
}
}
private void requestListening(ComponentName component) {
synchronized (mServices) {
CustomTile customTile = getTileForComponent(component);
if (customTile == null) {
Log.d("TileServices", "Couldn't find tile for " + component);
return;
}
TileServiceManager service = mServices.get(customTile);
if (service.getType() != TileService.TILE_MODE_ACTIVE) {
return;
}
service.setBindRequested(true);
try {
service.getTileService().onStartListening();
} catch (RemoteException e) {
}
}
}
@Override
public void setTileMode(ComponentName component, int mode) {
verifyCaller(component.getPackageName());
CustomTile customTile = getTileForComponent(component);
if (customTile != null) {
synchronized (mServices) {
mServices.get(customTile).setType(mode);
}
}
}
@Override
public void updateQsTile(Tile tile) {
verifyCaller(tile.getComponentName().getPackageName());
CustomTile customTile = getTileForComponent(tile.getComponentName());
if (customTile != null) {
mServices.get(customTile).setLastUpdate(System.currentTimeMillis());
synchronized (mServices) {
mServices.get(customTile).setLastUpdate(System.currentTimeMillis());
}
customTile.updateState(tile);
customTile.refreshState();
}
@@ -143,9 +183,21 @@ public class TileServices extends IQSService.Stub {
}
private CustomTile getTileForComponent(ComponentName component) {
return mTiles.get(component);
synchronized (mServices) {
return mTiles.get(component);
}
}
private final BroadcastReceiver mRequestListeningReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (TileService.ACTION_REQUEST_LISTENING.equals(intent.getAction())) {
requestListening(
(ComponentName) intent.getParcelableExtra(TileService.EXTRA_COMPONENT));
}
}
};
private static final Comparator<TileServiceManager> SERVICE_SORT =
new Comparator<TileServiceManager>() {
@Override

View File

@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.external;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -23,9 +24,13 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.TileService;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.test.AndroidTestCase;
import android.util.ArraySet;
import android.util.Log;
@@ -238,9 +243,49 @@ public class TileLifecycleManagerTests extends AndroidTestCase {
}
};
public static class FakeTileService extends TileService {
public static class FakeTileService extends Service {
public static final String ACTION_KILL = "com.android.systemui.test.KILL";
@Override
public IBinder onBind(Intent intent) {
return new IQSTileService.Stub() {
@Override
public void setQSService(IQSService service) {
}
@Override
public void setQSTile(Tile tile) throws RemoteException {
}
@Override
public void onTileAdded() throws RemoteException {
sendCallback("onTileAdded");
}
@Override
public void onTileRemoved() throws RemoteException {
sendCallback("onTileRemoved");
}
@Override
public void onStartListening() throws RemoteException {
sendCallback("onStartListening");
}
@Override
public void onStopListening() throws RemoteException {
sendCallback("onStopListening");
}
@Override
public void onClick(IBinder iBinder) throws RemoteException {
sendCallback("onClick");
}
};
}
@Override
public void onCreate() {
super.onCreate();
@@ -255,31 +300,6 @@ public class TileLifecycleManagerTests extends AndroidTestCase {
sendCallback("onDestroy");
}
@Override
public void onTileAdded() {
sendCallback("onTileAdded");
}
@Override
public void onTileRemoved() {
sendCallback("onTileRemoved");
}
@Override
public void onStartListening() {
sendCallback("onStartListening");
}
@Override
public void onStopListening() {
sendCallback("onStopListening");
}
@Override
public void onClick() {
sendCallback("onClick");
}
private void sendCallback(String callback) {
Log.d("TileLifecycleManager", "Relaying: " + callback);
sendBroadcast(new Intent(TILE_UPDATE_BROADCAST)

View File

@@ -15,8 +15,10 @@
*/
package com.android.systemui.qs.external;
import android.content.ComponentName;
import android.os.Handler;
import android.os.HandlerThread;
import android.service.quicksettings.TileService;
import com.android.systemui.SysuiTestCase;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -36,7 +38,13 @@ public class TileServiceManagerTests extends SysuiTestCase {
mThread.start();
mHandler = new Handler(mThread.getLooper());
mTileServices = Mockito.mock(TileServices.class);
Mockito.when(mTileServices.getContext()).thenReturn(mContext);
mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
ComponentName componentName = new ComponentName(mContext,
TileServiceManagerTests.class);
Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
mContext.getSharedPreferences(TileServiceManager.PREFS_FILE, 0).edit()
.putInt(componentName.flattenToString(), TileService.TILE_MODE_PASSIVE).commit();
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
}