cmsdk: Support deleteIntent and remove tiles when packages change.
Change-Id: I488410296c7579870406ea8fe289cf0b2158ea80
This commit is contained in:
parent
e84d6568ab
commit
fa82ebb308
|
@ -18,9 +18,16 @@ package org.cyanogenmod.platform.internal;
|
|||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
@ -59,6 +66,8 @@ public class CMStatusBarManagerService extends SystemService {
|
|||
|
||||
static final int MAX_PACKAGE_TILES = 4;
|
||||
|
||||
private static final int REASON_PACKAGE_CHANGED = 1;
|
||||
|
||||
private final ManagedServices.UserProfiles mUserProfiles = new ManagedServices.UserProfiles();
|
||||
|
||||
final ArrayList<ExternalQuickSettingsRecord> mQSTileList =
|
||||
|
@ -75,8 +84,94 @@ public class CMStatusBarManagerService extends SystemService {
|
|||
Log.d(TAG, "registerCMStatusBar cmstatusbar: " + this);
|
||||
mCustomTileListeners = new CustomTileListeners();
|
||||
publishBinderService(CMContextConstants.CM_STATUS_BAR_SERVICE, mService);
|
||||
|
||||
IntentFilter pkgFilter = new IntentFilter();
|
||||
pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
|
||||
pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
|
||||
pkgFilter.addDataScheme("package");
|
||||
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
|
||||
null);
|
||||
|
||||
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
|
||||
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
|
||||
null);
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean queryRestart = false;
|
||||
boolean queryRemove = false;
|
||||
boolean packageChanged = false;
|
||||
boolean removeTiles = true;
|
||||
|
||||
if (action.equals(Intent.ACTION_PACKAGE_ADDED)
|
||||
|| (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|
||||
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|
||||
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
|
||||
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|
||||
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
|
||||
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
|
||||
UserHandle.USER_ALL);
|
||||
String pkgList[] = null;
|
||||
boolean queryReplace = queryRemove &&
|
||||
intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
|
||||
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
|
||||
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
|
||||
} else if (queryRestart) {
|
||||
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
|
||||
} else {
|
||||
Uri uri = intent.getData();
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
String pkgName = uri.getSchemeSpecificPart();
|
||||
if (pkgName == null) {
|
||||
return;
|
||||
}
|
||||
if (packageChanged) {
|
||||
// We remove tiles for packages which have just been disabled
|
||||
try {
|
||||
final IPackageManager pm = AppGlobals.getPackageManager();
|
||||
final int enabled = pm.getApplicationEnabledSetting(pkgName,
|
||||
changeUserId != UserHandle.USER_ALL ? changeUserId :
|
||||
UserHandle.USER_OWNER);
|
||||
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
|
||||
removeTiles = false;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Package doesn't exist; probably racing with uninstall.
|
||||
// removeTiles is already true, so nothing to do here.
|
||||
Slog.i(TAG, "Exception trying to look up app enabled setting", e);
|
||||
} catch (RemoteException e) {
|
||||
// Failed to talk to PackageManagerService Should never happen!
|
||||
}
|
||||
}
|
||||
pkgList = new String[]{pkgName};
|
||||
}
|
||||
|
||||
if (pkgList != null && (pkgList.length > 0)) {
|
||||
for (String pkgName : pkgList) {
|
||||
if (removeTiles) {
|
||||
removeAllCustomTilesInt(pkgName, !queryRestart,
|
||||
changeUserId, REASON_PACKAGE_CHANGED, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
mCustomTileListeners.onPackagesChanged(queryReplace, pkgList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final IBinder mService = new ICMStatusBarManager.Stub() {
|
||||
/**
|
||||
* @hide
|
||||
|
@ -340,12 +435,81 @@ public class CMStatusBarManagerService extends SystemService {
|
|||
r.isCanceled = true;
|
||||
mCustomTileListeners.notifyRemovedLocked(r.sbTile);
|
||||
mCustomTileByKey.remove(r.sbTile.getKey());
|
||||
if (r.getCustomTile().deleteIntent != null) {
|
||||
try {
|
||||
r.getCustomTile().deleteIntent.send();
|
||||
} catch (PendingIntent.CanceledException ex) {
|
||||
// do nothing - there's no relevant way to recover, and
|
||||
// no reason to let this propagate
|
||||
Slog.w(TAG, "canceled PendingIntent for "
|
||||
+ r.sbTile.getPackage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all custom tiles from a given package that have all of the
|
||||
* {@code mustHaveFlags}.
|
||||
*/
|
||||
boolean removeAllCustomTilesInt(String pkg, boolean doit, int userId, int reason,
|
||||
ManagedServices.ManagedServiceInfo listener) {
|
||||
synchronized (mQSTileList) {
|
||||
final int N = mQSTileList.size();
|
||||
ArrayList<ExternalQuickSettingsRecord> removedTiles = null;
|
||||
for (int i = N-1; i >= 0; --i) {
|
||||
ExternalQuickSettingsRecord r = mQSTileList.get(i);
|
||||
if (!customTileMatchesUserId(r, userId)) {
|
||||
continue;
|
||||
}
|
||||
// Don't remove custom tiles to all, if there's no package name specified
|
||||
if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
|
||||
continue;
|
||||
}
|
||||
if (pkg != null && !r.sbTile.getPackage().equals(pkg)) {
|
||||
continue;
|
||||
}
|
||||
if (removedTiles == null) {
|
||||
removedTiles = new ArrayList<>();
|
||||
}
|
||||
removedTiles.add(r);
|
||||
if (!doit) {
|
||||
return true;
|
||||
}
|
||||
mQSTileList.remove(i);
|
||||
removeCustomTileLocked(r, false, reason);
|
||||
}
|
||||
return removedTiles != null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCustomTileLocked(ExternalQuickSettingsRecord r,
|
||||
boolean sendDelete, int reason) {
|
||||
// tell the app
|
||||
if (sendDelete) {
|
||||
if (r.getCustomTile().deleteIntent != null) {
|
||||
try {
|
||||
r.getCustomTile().deleteIntent.send();
|
||||
} catch (PendingIntent.CanceledException ex) {
|
||||
// do nothing - there's no relevant way to recover, and
|
||||
// no reason to let this propagate
|
||||
Slog.w(TAG, "canceled PendingIntent for " + r.sbTile.getPackage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status bar
|
||||
if (r.getCustomTile().icon != 0 || r.getCustomTile().remoteIcon != null) {
|
||||
r.isCanceled = true;
|
||||
mCustomTileListeners.notifyRemovedLocked(r.sbTile);
|
||||
}
|
||||
|
||||
mCustomTileByKey.remove(r.sbTile.getKey());
|
||||
}
|
||||
|
||||
private void enforceSystemOrSystemUI(String message) {
|
||||
if (isCallerSystem()) return;
|
||||
mContext.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
|
||||
|
|
|
@ -61,6 +61,14 @@ public class CustomTile implements Parcelable {
|
|||
*/
|
||||
public Intent onSettingsClick;
|
||||
|
||||
/**
|
||||
* The intent to execute when the custom tile is explicitly removed by the user.
|
||||
*
|
||||
* This probably shouldn't be launching an activity since several of those will be sent
|
||||
* at the same time.
|
||||
*/
|
||||
public PendingIntent deleteIntent;
|
||||
|
||||
/**
|
||||
* An optional Uri to be parsed and broadcast on tile click, if an onClick pending intent
|
||||
* is specified, it will take priority over the uri to be broadcasted.
|
||||
|
@ -141,6 +149,9 @@ public class CustomTile implements Parcelable {
|
|||
if (parcel.readInt() != 0) {
|
||||
this.remoteIcon = Bitmap.CREATOR.createFromParcel(parcel);
|
||||
}
|
||||
if (parcel.readInt() != 0) {
|
||||
this.deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel);
|
||||
}
|
||||
}
|
||||
|
||||
parcel.setDataPosition(startPosition + parcelableSize);
|
||||
|
@ -196,6 +207,9 @@ public class CustomTile implements Parcelable {
|
|||
if (remoteIcon != null) {
|
||||
b.append("remoteIcon=" + remoteIcon.getGenerationId() + NEW_LINE);
|
||||
}
|
||||
if (deleteIntent != null) {
|
||||
b.append("deleteIntent=" + deleteIntent.toString() + NEW_LINE);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
@ -214,6 +228,7 @@ public class CustomTile implements Parcelable {
|
|||
that.icon = this.icon;
|
||||
that.collapsePanel = this.collapsePanel;
|
||||
that.remoteIcon = this.remoteIcon;
|
||||
that.deleteIntent = this.deleteIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -283,6 +298,13 @@ public class CustomTile implements Parcelable {
|
|||
out.writeInt(0);
|
||||
}
|
||||
|
||||
if (deleteIntent != null) {
|
||||
out.writeInt(1);
|
||||
deleteIntent.writeToParcel(out, 0);
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
|
||||
// Go back and write size
|
||||
int parcelableSize = out.dataPosition() - startPosition;
|
||||
out.setDataPosition(sizePosition);
|
||||
|
@ -885,6 +907,7 @@ public class CustomTile implements Parcelable {
|
|||
private Context mContext;
|
||||
private ExpandedStyle mExpandedStyle;
|
||||
private boolean mCollapsePanel = true;
|
||||
private PendingIntent mDeleteIntent;
|
||||
|
||||
/**
|
||||
* Constructs a new Builder with the defaults:
|
||||
|
@ -1015,6 +1038,19 @@ public class CustomTile implements Parcelable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply a {@link PendingIntent} to send when the custom tile is cleared explicitly
|
||||
* by the user.
|
||||
*
|
||||
* @see CustomTile#deleteIntent
|
||||
* @param intent
|
||||
* @return {@link cyanogenmod.app.CustomTile.Builder}
|
||||
*/
|
||||
public Builder setDeleteIntent(PendingIntent intent) {
|
||||
mDeleteIntent = intent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link cyanogenmod.app.CustomTile} object
|
||||
* @return {@link cyanogenmod.app.CustomTile}
|
||||
|
@ -1031,6 +1067,7 @@ public class CustomTile implements Parcelable {
|
|||
tile.icon = mIcon;
|
||||
tile.collapsePanel = mCollapsePanel;
|
||||
tile.remoteIcon = mRemoteIcon;
|
||||
tile.deleteIntent = mDeleteIntent;
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,6 +148,22 @@ public class CMStatusBarTest extends TestActivity {
|
|||
}
|
||||
},
|
||||
|
||||
new Test("test publish tile with delete intent") {
|
||||
public void run() {
|
||||
Intent intent = new Intent(CMStatusBarTest.this, DummySettings.class);
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(CMStatusBarTest.this, 0, intent, 0);
|
||||
CustomTile customTile = new CustomTile.Builder(CMStatusBarTest.this)
|
||||
.setLabel("Test Settings From SDK")
|
||||
.setIcon(R.drawable.ic_launcher)
|
||||
.setDeleteIntent(pendingIntent)
|
||||
.setContentDescription("Content description")
|
||||
.build();
|
||||
CMStatusBarManager.getInstance(CMStatusBarTest.this)
|
||||
.publishTile(CUSTOM_TILE_SETTINGS_ID, customTile);
|
||||
}
|
||||
},
|
||||
|
||||
new Test("test publish tile with custom uri") {
|
||||
public void run() {
|
||||
CustomTile customTile = new CustomTile.Builder(CMStatusBarTest.this)
|
||||
|
|
|
@ -67,6 +67,17 @@ public class CustomTileBuilderTest extends AndroidTestCase {
|
|||
assertEquals(intent, customTile.onSettingsClick);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCustomTileBuilderDeleteIntent() {
|
||||
Intent intent = new Intent(mContext, DummySettings.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
|
||||
CustomTile customTile = new CustomTile.Builder(mContext)
|
||||
.setDeleteIntent(pendingIntent)
|
||||
.build();
|
||||
assertNotNull(customTile.deleteIntent);
|
||||
assertEquals(pendingIntent, customTile.deleteIntent);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCustomTileBuilderOnClickUri() {
|
||||
//Calling Mike Jones, WHO!? MIKE JONES.
|
||||
|
|
Loading…
Reference in New Issue