aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cm/lib/main/java/org/cyanogenmod/platform/internal/CMStatusBarManagerService.java164
-rw-r--r--src/java/cyanogenmod/app/CustomTile.java37
-rw-r--r--tests/src/org/cyanogenmod/tests/customtiles/CMStatusBarTest.java16
-rw-r--r--tests/src/org/cyanogenmod/tests/customtiles/unit/CustomTileBuilderTest.java11
4 files changed, 228 insertions, 0 deletions
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/CMStatusBarManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMStatusBarManagerService.java
index 4ad890c..7ad0b3b 100644
--- a/cm/lib/main/java/org/cyanogenmod/platform/internal/CMStatusBarManagerService.java
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/CMStatusBarManagerService.java
@@ -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,
diff --git a/src/java/cyanogenmod/app/CustomTile.java b/src/java/cyanogenmod/app/CustomTile.java
index d6e7e2d..7e57fd8 100644
--- a/src/java/cyanogenmod/app/CustomTile.java
+++ b/src/java/cyanogenmod/app/CustomTile.java
@@ -62,6 +62,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:
@@ -1016,6 +1039,19 @@ public class CustomTile implements Parcelable {
}
/**
+ * 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;
}
}
diff --git a/tests/src/org/cyanogenmod/tests/customtiles/CMStatusBarTest.java b/tests/src/org/cyanogenmod/tests/customtiles/CMStatusBarTest.java
index 4251707..20c9809 100644
--- a/tests/src/org/cyanogenmod/tests/customtiles/CMStatusBarTest.java
+++ b/tests/src/org/cyanogenmod/tests/customtiles/CMStatusBarTest.java
@@ -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)
diff --git a/tests/src/org/cyanogenmod/tests/customtiles/unit/CustomTileBuilderTest.java b/tests/src/org/cyanogenmod/tests/customtiles/unit/CustomTileBuilderTest.java
index 93bd132..713d607 100644
--- a/tests/src/org/cyanogenmod/tests/customtiles/unit/CustomTileBuilderTest.java
+++ b/tests/src/org/cyanogenmod/tests/customtiles/unit/CustomTileBuilderTest.java
@@ -68,6 +68,17 @@ public class CustomTileBuilderTest extends AndroidTestCase {
}
@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.
Uri uri = Uri.parse("2813308004");