Merge "Sending MY_PACKAGE_SUSPENDED to suspended apps" into pi-dev
diff --git a/api/current.txt b/api/current.txt
index d721936..052b7f8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9905,6 +9905,8 @@
     field public static final java.lang.String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
     field public static final java.lang.String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED";
     field public static final java.lang.String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
+    field public static final java.lang.String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
+    field public static final java.lang.String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
@@ -10065,6 +10067,7 @@
     field public static final java.lang.String EXTRA_SPLIT_NAME = "android.intent.extra.SPLIT_NAME";
     field public static final java.lang.String EXTRA_STREAM = "android.intent.extra.STREAM";
     field public static final java.lang.String EXTRA_SUBJECT = "android.intent.extra.SUBJECT";
+    field public static final java.lang.String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
     field public static final java.lang.String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
     field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
     field public static final java.lang.String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -11178,7 +11181,7 @@
     method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
-    method public android.os.PersistableBundle getSuspendedPackageAppExtras();
+    method public android.os.Bundle getSuspendedPackageAppExtras();
     method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public abstract java.lang.String[] getSystemSharedLibraryNames();
     method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f38c80c..a68136b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2167,15 +2167,16 @@
     @Override
     public PersistableBundle getSuspendedPackageAppExtras(String packageName) {
         try {
-            return mPM.getPackageSuspendedAppExtras(packageName, mContext.getUserId());
+            return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
-    public PersistableBundle getSuspendedPackageAppExtras() {
-        return getSuspendedPackageAppExtras(mContext.getOpPackageName());
+    public Bundle getSuspendedPackageAppExtras() {
+        final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName());
+        return extras != null ? new Bundle(extras.deepCopy()) : null;
     }
 
     @Override
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ce32278..02f0ded 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1814,6 +1814,17 @@
     public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
 
     /**
+     * Intent extra: A {@link Bundle} of extras for a package being suspended. Will be sent with
+     * {@link #ACTION_MY_PACKAGE_SUSPENDED}.
+     *
+     * @see #ACTION_MY_PACKAGE_SUSPENDED
+     * @see #ACTION_MY_PACKAGE_UNSUSPENDED
+     * @see PackageManager#isPackageSuspended()
+     * @see PackageManager#getSuspendedPackageAppExtras()
+     */
+    public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
+
+    /**
      * Intent extra: An app split name.
      * <p>
      * Type: String
@@ -2237,6 +2248,43 @@
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
+
+    /**
+     * Broadcast Action: Sent to a package that has been suspended by the system. This is sent
+     * whenever a package is put into a suspended state or any of it's app extras change while
+     * in the suspended state.
+     * <p> Optionally includes the following extras:
+     * <ul>
+     *     <li> {@link #EXTRA_SUSPENDED_PACKAGE_EXTRAS} which is a {@link Bundle} which will contain
+     *     useful information for the app being suspended.
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system. <em>This will be delivered to {@link BroadcastReceiver} components declared in
+     * the manifest.</em>
+     *
+     * @see #ACTION_MY_PACKAGE_UNSUSPENDED
+     * @see #EXTRA_SUSPENDED_PACKAGE_EXTRAS
+     * @see PackageManager#isPackageSuspended()
+     * @see PackageManager#getSuspendedPackageAppExtras()
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
+
+    /**
+     * Broadcast Action: Sent to a package that has been unsuspended.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system. <em>This will be delivered to {@link BroadcastReceiver} components declared in
+     * the manifest.</em>
+     *
+     * @see #ACTION_MY_PACKAGE_SUSPENDED
+     * @see #EXTRA_SUSPENDED_PACKAGE_EXTRAS
+     * @see PackageManager#isPackageSuspended()
+     * @see PackageManager#getSuspendedPackageAppExtras()
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
+
     /**
      * Broadcast Action: A user ID has been removed from the system.  The user
      * ID number is stored in the extra data under {@link #EXTRA_UID}.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index d43d80f..1352b5e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -278,7 +278,7 @@
 
     boolean isPackageSuspendedForUser(String packageName, int userId);
 
-    PersistableBundle getPackageSuspendedAppExtras(String pacakgeName, int userId);
+    PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
 
     void setSuspendedPackageAppExtras(String packageName, in PersistableBundle appExtras,
             int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 314eb98..491f0af 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5513,7 +5513,7 @@
      * Puts the package in a suspended state, where attempts at starting activities are denied.
      *
      * <p>It doesn't remove the data or the actual package file. The application's notifications
-     * will be hidden, any of the it's started activities will be stopped and it will not be able to
+     * will be hidden, any of its started activities will be stopped and it will not be able to
      * show toasts or dialogs or ring the device. When the user tries to launch a suspended app, a
      * system dialog with the given {@code dialogMessage} will be shown instead.</p>
      *
@@ -5577,11 +5577,26 @@
     }
 
     /**
-     * Apps can query this to know if they have been suspended.
+     * Apps can query this to know if they have been suspended. A system app with the permission
+     * {@code android.permission.SUSPEND_APPS} can put any app on the device into a suspended state.
+     *
+     * <p>While in this state, the application's notifications will be hidden, any of its started
+     * activities will be stopped and it will not be able to show toasts or dialogs or ring the
+     * device. When the user tries to launch a suspended app, the system will, instead, show a
+     * dialog to the user informing them that they cannot use this app while it is suspended.
+     *
+     * <p>When an app is put into this state, the broadcast action
+     * {@link Intent#ACTION_MY_PACKAGE_SUSPENDED} will be delivered to any of its broadcast
+     * receivers that included this action in their intent-filters, <em>including manifest
+     * receivers.</em> Similarly, a broadcast action {@link Intent#ACTION_MY_PACKAGE_UNSUSPENDED}
+     * is delivered when a previously suspended app is taken out of this state.
+     * </p>
      *
      * @return {@code true} if the calling package has been suspended, {@code false} otherwise.
      *
      * @see #getSuspendedPackageAppExtras()
+     * @see Intent#ACTION_MY_PACKAGE_SUSPENDED
+     * @see Intent#ACTION_MY_PACKAGE_UNSUSPENDED
      */
     public boolean isPackageSuspended() {
         throw new UnsupportedOperationException("isPackageSuspended not implemented");
@@ -5602,7 +5617,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.SUSPEND_APPS)
-    public PersistableBundle getSuspendedPackageAppExtras(String packageName) {
+    public @Nullable PersistableBundle getSuspendedPackageAppExtras(String packageName) {
         throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented");
     }
 
@@ -5631,15 +5646,17 @@
      * Returns any extra information supplied as {@code appExtras} to the system when the calling
      * app was suspended.
      *
-     * <p> Note: This just returns whatever {@link PersistableBundle} was passed to the system via
-     * {@code setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
-     * String)} when suspending the package, <em> which might be {@code null}. </em></p>
+     * <p>Note: If no extras were supplied to the system, this method will return {@code null}, even
+     * when the calling app has been suspended.</p>
      *
-     * @return A {@link PersistableBundle} containing the extras for the app, or {@code null} if the
+     * @return A {@link Bundle} containing the extras for the app, or {@code null} if the
      * package is not currently suspended.
+     *
      * @see #isPackageSuspended()
+     * @see Intent#ACTION_MY_PACKAGE_UNSUSPENDED
+     * @see Intent#ACTION_MY_PACKAGE_SUSPENDED
      */
-    public @Nullable PersistableBundle getSuspendedPackageAppExtras() {
+    public @Nullable Bundle getSuspendedPackageAppExtras() {
         throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented");
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3fb52dc..1c3448b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -89,6 +89,8 @@
     <protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
     <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.USER_ACTIVITY_NOTIFICATION" />
+    <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
+    <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
 
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9b67bbd..e29a55b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -92,6 +92,7 @@
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.internal.util.ArrayUtils.appendElement;
 import static com.android.internal.util.ArrayUtils.appendInt;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -13987,18 +13988,15 @@
             return packageNames;
         }
 
-        // List of package names for whom the suspended state has changed.
-        final List<String> changedPackages = new ArrayList<>(packageNames.length);
-        // List of package names for whom the suspended state is not set as requested in this
-        // method.
+        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
         final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mPackages) {
                 for (int i = 0; i < packageNames.length; i++) {
                     final String packageName = packageNames[i];
-                    if (packageName == callingPackage) {
-                        Slog.w(TAG, "Calling package: " + callingPackage + "trying to "
+                    if (callingPackage.equals(packageName)) {
+                        Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
                                 + (suspended ? "" : "un") + "suspend itself. Ignoring");
                         unactionedPackages.add(packageName);
                         continue;
@@ -14018,17 +14016,18 @@
                         }
                         pkgSetting.setSuspended(suspended, callingPackage, appExtras,
                                 launcherExtras, userId);
-                        changedPackages.add(packageName);
+                        changedPackagesList.add(packageName);
                     }
                 }
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
-        // TODO (b/75036698): Also send each package a broadcast when suspended state changed
-        if (!changedPackages.isEmpty()) {
-            sendPackagesSuspendedForUser(changedPackages.toArray(
-                    new String[changedPackages.size()]), userId, suspended);
+        if (!changedPackagesList.isEmpty()) {
+            final String[] changedPackages = changedPackagesList.toArray(
+                    new String[changedPackagesList.size()]);
+            sendPackagesSuspendedForUser(changedPackages, userId, suspended);
+            sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, appExtras, userId);
             synchronized (mPackages) {
                 scheduleWritePackageRestrictionsLocked(userId);
             }
@@ -14038,7 +14037,7 @@
     }
 
     @Override
-    public PersistableBundle getPackageSuspendedAppExtras(String packageName, int userId) {
+    public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
         if (getPackageUid(packageName, 0, userId) != callingUid) {
             mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
@@ -14049,7 +14048,10 @@
                 throw new IllegalArgumentException("Unknown target package: " + packageName);
             }
             final PackageUserState packageUserState = ps.readUserState(userId);
-            return packageUserState.suspended ? packageUserState.suspendedAppExtras : null;
+            if (packageUserState.suspended) {
+                return packageUserState.suspendedAppExtras;
+            }
+            return null;
         }
     }
 
@@ -14065,12 +14067,49 @@
             }
             final PackageUserState packageUserState = ps.readUserState(userId);
             if (packageUserState.suspended) {
-                // TODO (b/75036698): Also send this package a broadcast with the new app extras
                 packageUserState.suspendedAppExtras = appExtras;
+                sendMyPackageSuspendedOrUnsuspended(new String[] {packageName}, true, appExtras,
+                        userId);
             }
         }
     }
 
+    private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
+            PersistableBundle appExtras, int userId) {
+        final String action;
+        final Bundle intentExtras = new Bundle();
+        if (suspended) {
+            action = Intent.ACTION_MY_PACKAGE_SUSPENDED;
+            if (appExtras != null) {
+                final Bundle bundledAppExtras = new Bundle(appExtras.deepCopy());
+                intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, bundledAppExtras);
+            }
+        } else {
+            action = Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    final IActivityManager am = ActivityManager.getService();
+                    if (am == null) {
+                        Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+                                + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
+                        return;
+                    }
+                    final int[] targetUserIds = new int[] {userId};
+                    for (String packageName : affectedPackages) {
+                        doSendBroadcast(am, action, null, intentExtras,
+                                Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
+                                targetUserIds, false);
+                    }
+                } catch (RemoteException ex) {
+                    // Shouldn't happen as AMS is in the same process.
+                }
+            }
+        });
+    }
+
     @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index d702318..ee0ad39 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -16,55 +16,129 @@
 
 package com.android.server.pm;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.app.AppGlobals;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.os.BaseBundle;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 @RunWith(AndroidJUnit4.class)
-@MediumTest
+@LargeTest
 public class SuspendPackagesTest {
     private static final String TEST_APP_PACKAGE_NAME = SuspendTestReceiver.PACKAGE_NAME;
     private static final String[] PACKAGES_TO_SUSPEND = new String[]{TEST_APP_PACKAGE_NAME};
 
+    public static final String INSTRUMENTATION_PACKAGE = "com.android.frameworks.servicestests";
+    public static final String ACTION_REPORT_MY_PACKAGE_SUSPENDED =
+            INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_SUSPENDED";
+    public static final String ACTION_REPORT_MY_PACKAGE_UNSUSPENDED =
+            INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_UNSUSPENDED";
+
     private Context mContext;
     private PackageManager mPackageManager;
     private Handler mReceiverHandler;
     private ComponentName mTestReceiverComponent;
+    private AppCommunicationReceiver mAppCommsReceiver;
+
+    private static final class AppCommunicationReceiver extends BroadcastReceiver {
+        private Context context;
+        private boolean registered;
+        private SynchronousQueue<Intent> intentQueue = new SynchronousQueue<>();
+
+        AppCommunicationReceiver(Context context) {
+            this.context = context;
+        }
+
+        void register(Handler handler) {
+            registered = true;
+            final IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(ACTION_REPORT_MY_PACKAGE_SUSPENDED);
+            intentFilter.addAction(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
+            context.registerReceiver(this, intentFilter, null, handler);
+        }
+
+        void unregister() {
+            if (registered) {
+                context.unregisterReceiver(this);
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                intentQueue.offer(intent, 5, TimeUnit.SECONDS);
+            } catch (InterruptedException ie) {
+                throw new RuntimeException("Receiver thread interrupted", ie);
+            }
+        }
+
+        Intent receiveIntentFromApp() {
+            if (!registered) {
+                throw new IllegalStateException("Receiver not registered");
+            }
+            final Intent intent;
+            try {
+                intent = intentQueue.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException ie) {
+                throw new RuntimeException("Interrupted while waiting for app broadcast", ie);
+            }
+            assertNotNull("No intent received from app within 5 seconds", intent);
+            return intent;
+        }
+    }
 
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
         mPackageManager = mContext.getPackageManager();
-        mPackageManager.setPackagesSuspended(PACKAGES_TO_SUSPEND, false, null, null, null);
         mReceiverHandler = new Handler(Looper.getMainLooper());
         mTestReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
                 SuspendTestReceiver.class.getCanonicalName());
+        IPackageManager ipm = AppGlobals.getPackageManager();
+        try {
+            // Otherwise implicit broadcasts will not be delivered.
+            ipm.setPackageStoppedState(TEST_APP_PACKAGE_NAME, false, mContext.getUserId());
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        unsuspendTestPackage();
+        mAppCommsReceiver = new AppCommunicationReceiver(mContext);
     }
 
+    /**
+     * Care should be taken when used with {@link #mAppCommsReceiver} in the same test as both use
+     * the same handler.
+     */
     private Bundle requestAppAction(String action) throws InterruptedException {
         final AtomicReference<Bundle> result = new AtomicReference<>();
         final CountDownLatch receiverLatch = new CountDownLatch(1);
@@ -98,6 +172,24 @@
         assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
     }
 
+    private void unsuspendTestPackage() {
+        final String[] unchangedPackages = mPackageManager.setPackagesSuspended(
+                PACKAGES_TO_SUSPEND, false, null, null, null);
+        assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
+    }
+
+
+    private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) {
+        if (expected != null) {
+            expected.get(""); // hack to unparcel the bundles.
+        }
+        if (received != null) {
+            received.get("");
+        }
+        assertTrue(message + ": [expected: " + expected + "; received: " + received + "]",
+                BaseBundle.kindofEquals(expected, received));
+    }
+
     @Test
     public void testIsPackageSuspended() {
         suspendTestPackage(null, null);
@@ -109,19 +201,73 @@
     public void testSuspendedStateFromApp() throws Exception {
         Bundle resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
         assertFalse(resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED, true));
-        assertNull(resultFromApp.getParcelable(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+        assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
 
-        final PersistableBundle appExtras = getExtras("appExtras", 20, "20", 0.2);
+        final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2);
         suspendTestPackage(appExtras, null);
 
         resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
         assertTrue("resultFromApp:suspended is false",
                 resultFromApp.getBoolean(SuspendTestReceiver.EXTRA_SUSPENDED));
-        final PersistableBundle receivedAppExtras =
-                resultFromApp.getParcelable(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS);
-        receivedAppExtras.get(""); // hack to unparcel the bundles
-        appExtras.get("");
-        assertTrue("Received app extras " + receivedAppExtras + " different to the ones supplied",
-                BaseBundle.kindofEquals(appExtras, receivedAppExtras));
+        final Bundle receivedAppExtras =
+                resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS);
+        assertSameExtras("Received app extras different to the ones supplied",
+                appExtras, receivedAppExtras);
+    }
+
+    @Test
+    public void testMyPackageSuspendedUnsuspended() {
+        mAppCommsReceiver.register(mReceiverHandler);
+        final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1);
+        suspendTestPackage(appExtras, null);
+        Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+        assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+        assertSameExtras("Received app extras different to the ones supplied", appExtras,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+        unsuspendTestPackage();
+        intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+        assertTrue("MY_PACKAGE_UNSUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_UNSUSPENDED.equals(intentFromApp.getAction()));
+    }
+
+    @Test
+    public void testUpdatingAppExtras() {
+        mAppCommsReceiver.register(mReceiverHandler);
+        final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1,
+                "1", 0.1);
+        suspendTestPackage(extras1, null);
+        Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+        assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+        assertSameExtras("Received app extras different to the ones supplied", extras1,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+        final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
+                "2", 0.2);
+        mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2);
+        intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
+        assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
+                ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
+        assertSameExtras("Received app extras different to the updated extras", extras2,
+                intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
+    }
+
+    @Test
+    public void testCannotSuspendSelf() {
+        final String[] unchangedPkgs = mPackageManager.setPackagesSuspended(
+                new String[]{mContext.getOpPackageName()}, true, null, null, null);
+        assertTrue(unchangedPkgs.length == 1);
+        assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAppCommsReceiver.unregister();
+        Thread.sleep(250); // To prevent any race with the next registerReceiver
+    }
+
+    @FunctionalInterface
+    interface Condition {
+        boolean isTrue();
     }
 }
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
index 40a34b9..afdde72 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/Android.mk
@@ -17,14 +17,18 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
 
 LOCAL_COMPATIBILITY_SUITE := device-tests
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES += ../../src/com/android/server/pm/SuspendPackagesTest.java
 
 LOCAL_PACKAGE_NAME := SuspendTestApp
 LOCAL_DEX_PREOPT := false
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
 include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml
index 70a1fd0..ce6a27a 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/AndroidManifest.xml
@@ -21,7 +21,12 @@
         <activity android:name=".SuspendTestActivity"
                   android:exported="true" />
         <receiver android:name=".SuspendTestReceiver"
-                  android:exported="true" />
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
+                <action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
+            </intent-filter>
+        </receiver>
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java
index 6f353a0..90a9f01 100644
--- a/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java
+++ b/services/tests/servicestests/test-apps/SuspendTestApp/src/com/android/servicestests/apps/suspendtestapp/SuspendTestReceiver.java
@@ -16,12 +16,15 @@
 
 package com.android.servicestests.apps.suspendtestapp;
 
+import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_MY_PACKAGE_SUSPENDED;
+import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_MY_PACKAGE_UNSUSPENDED;
+import static com.android.server.pm.SuspendPackagesTest.INSTRUMENTATION_PACKAGE;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.os.PersistableBundle;
 import android.util.Log;
 
 public class SuspendTestReceiver extends BroadcastReceiver {
@@ -34,21 +37,33 @@
     public static final String EXTRA_SUSPENDED_APP_EXTRAS =
             PACKAGE_NAME + ".extra.SUSPENDED_APP_EXTRAS";
 
-    private PackageManager mPm;
-
     @Override
     public void onReceive(Context context, Intent intent) {
-        mPm = context.getPackageManager();
-        Log.d(TAG, "Received request action " + intent.getAction());
+        final PackageManager packageManager = context.getPackageManager();
+        Log.d(TAG, "Received action " + intent.getAction());
+        final Bundle appExtras;
         switch (intent.getAction()) {
             case ACTION_GET_SUSPENDED_STATE:
                 final Bundle result = new Bundle();
-                final boolean suspended = mPm.isPackageSuspended();
-                final PersistableBundle appExtras = mPm.getSuspendedPackageAppExtras();
+                final boolean suspended = packageManager.isPackageSuspended();
+                appExtras = packageManager.getSuspendedPackageAppExtras();
                 result.putBoolean(EXTRA_SUSPENDED, suspended);
-                result.putParcelable(EXTRA_SUSPENDED_APP_EXTRAS, appExtras);
+                result.putBundle(EXTRA_SUSPENDED_APP_EXTRAS, appExtras);
                 setResult(0, null, result);
                 break;
+            case Intent.ACTION_MY_PACKAGE_SUSPENDED:
+                appExtras = intent.getBundleExtra(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS);
+                final Intent reportSuspendIntent = new Intent(ACTION_REPORT_MY_PACKAGE_SUSPENDED)
+                        .putExtra(EXTRA_SUSPENDED_APP_EXTRAS, appExtras)
+                        .setPackage(INSTRUMENTATION_PACKAGE);
+                context.sendBroadcast(reportSuspendIntent);
+                break;
+            case Intent.ACTION_MY_PACKAGE_UNSUSPENDED:
+                final Intent reportUnsuspendIntent =
+                        new Intent(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED)
+                        .setPackage(INSTRUMENTATION_PACKAGE);
+                context.sendBroadcast(reportUnsuspendIntent);
+                break;
             default:
                 Log.e(TAG, "Unknown action: " + intent.getAction());
         }