Loading core/java/android/content/pm/PackageManager.java +4 −4 Original line number Diff line number Diff line Loading @@ -5825,16 +5825,16 @@ public abstract class PackageManager { * {@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 * activities will be stopped and it will not be able to show toasts or dialogs or play audio. * 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> * is delivered when a previously suspended app is taken out of this state. Apps are expected to * use these to gracefully deal with transitions to and from this state. * * @return {@code true} if the calling package has been suspended, {@code false} otherwise. * Loading services/core/java/com/android/server/AppOpsService.java +24 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; Loading @@ -36,8 +37,11 @@ import android.app.AppOpsManager.HistoricalOpEntry; import android.app.AppOpsManager.HistoricalPackageOps; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.content.BroadcastReceiver; import android.content.ContentResolver; 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; Loading Loading @@ -645,6 +649,26 @@ public class AppOpsService extends IAppOpsService.Stub { } } final IntentFilter packageSuspendFilter = new IntentFilter(); packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] changedPkgs = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO); for (int i = 0; i < changedUids.length; i++) { final int changedUid = changedUids[i]; final String changedPkg = changedPkgs[i]; // We trust packagemanager to insert matching uid and packageNames in the extras mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, AppOpsService.this, callbacks, OP_PLAY_AUDIO, changedUid, changedPkg)); } } }, packageSuspendFilter); PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); packageManagerInternal.setExternalSourcesPolicy( Loading services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; Loading @@ -35,12 +39,14 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.res.Resources; import android.media.AudioAttributes; 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.os.ServiceManager; import android.os.UserHandle; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; Loading @@ -54,6 +60,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.servicestests.apps.suspendtestapp.SuspendTestActivity; import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; Loading Loading @@ -550,6 +558,32 @@ public class SuspendPackagesTest { assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction()); } @Test public void testAudioOpBlockedOnSuspend() throws Exception { final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); final CountDownLatch latch = new CountDownLatch(1); final IAppOpsCallback watcher = new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { if (op == OP_PLAY_AUDIO && packageName.equals(TEST_APP_PACKAGE_NAME)) { latch.countDown(); } } }; iAppOps.startWatchingMode(OP_PLAY_AUDIO, TEST_APP_PACKAGE_NAME, watcher); final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0); int audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); assertEquals("Audio muted for unsuspended package", MODE_ALLOWED, audioOpMode); suspendTestPackage(null, null, null); assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS)); audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); assertEquals("Audio not muted for suspended package", MODE_IGNORED, audioOpMode); iAppOps.stopWatchingMode(watcher); } @After public void tearDown() throws IOException { mAppCommsReceiver.unregister(); Loading Loading
core/java/android/content/pm/PackageManager.java +4 −4 Original line number Diff line number Diff line Loading @@ -5825,16 +5825,16 @@ public abstract class PackageManager { * {@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 * activities will be stopped and it will not be able to show toasts or dialogs or play audio. * 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> * is delivered when a previously suspended app is taken out of this state. Apps are expected to * use these to gracefully deal with transitions to and from this state. * * @return {@code true} if the calling package has been suspended, {@code false} otherwise. * Loading
services/core/java/com/android/server/AppOpsService.java +24 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; Loading @@ -36,8 +37,11 @@ import android.app.AppOpsManager.HistoricalOpEntry; import android.app.AppOpsManager.HistoricalPackageOps; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.content.BroadcastReceiver; import android.content.ContentResolver; 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; Loading Loading @@ -645,6 +649,26 @@ public class AppOpsService extends IAppOpsService.Stub { } } final IntentFilter packageSuspendFilter = new IntentFilter(); packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] changedPkgs = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO); for (int i = 0; i < changedUids.length; i++) { final int changedUid = changedUids[i]; final String changedPkg = changedPkgs[i]; // We trust packagemanager to insert matching uid and packageNames in the extras mHandler.sendMessage(PooledLambda.obtainMessage(AppOpsService::notifyOpChanged, AppOpsService.this, callbacks, OP_PLAY_AUDIO, changedUid, changedPkg)); } } }, packageSuspendFilter); PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); packageManagerInternal.setExternalSourcesPolicy( Loading
services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; Loading @@ -35,12 +39,14 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.res.Resources; import android.media.AudioAttributes; 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.os.ServiceManager; import android.os.UserHandle; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; Loading @@ -54,6 +60,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.servicestests.apps.suspendtestapp.SuspendTestActivity; import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver; Loading Loading @@ -550,6 +558,32 @@ public class SuspendPackagesTest { assertEquals(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction()); } @Test public void testAudioOpBlockedOnSuspend() throws Exception { final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); final CountDownLatch latch = new CountDownLatch(1); final IAppOpsCallback watcher = new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { if (op == OP_PLAY_AUDIO && packageName.equals(TEST_APP_PACKAGE_NAME)) { latch.countDown(); } } }; iAppOps.startWatchingMode(OP_PLAY_AUDIO, TEST_APP_PACKAGE_NAME, watcher); final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0); int audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); assertEquals("Audio muted for unsuspended package", MODE_ALLOWED, audioOpMode); suspendTestPackage(null, null, null); assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS)); audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO, AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME); assertEquals("Audio not muted for suspended package", MODE_IGNORED, audioOpMode); iAppOps.stopWatchingMode(watcher); } @After public void tearDown() throws IOException { mAppCommsReceiver.unregister(); Loading