From 2e14118f432aadebdc0eb42eea146b638182d34b Mon Sep 17 00:00:00 2001 From: Galia Peycheva Date: Fri, 9 Jun 2023 16:06:58 +0000 Subject: [PATCH] Do not kill dream process when stopping dream The DreamController currently force stops the dream with ActivityTaskManagerService.removeRootTask. Unintuitively, that method kills the client process if the dream task is the last task in the process. We want to keep the dream client process warm and leave it up to the LMK to decide when to kill it. In this CL we pass an IAppTask to the DreamController, which allows it to stop the dream task without killing the process. Bug: 281960746 Test: atest DreamManagerServiceTests Test: atest DreamOverlayTest Test: atest DreamServiceTest Change-Id: I83f267657812cb776fc65919bc0f5727fdc15b30 --- .../android/app/IActivityTaskManager.aidl | 9 --- .../android/service/dreams/DreamService.java | 5 +- .../android/service/dreams/IDreamManager.aidl | 2 + .../server/dreams/DreamController.java | 34 +++++++++- .../server/dreams/DreamManagerService.java | 62 ++++++++++++++++++ .../wm/ActivityTaskManagerInternal.java | 7 +++ .../server/wm/ActivityTaskManagerService.java | 63 ++++++++----------- 7 files changed, 129 insertions(+), 53 deletions(-) diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4e2b6fa56b178..d189bab85195e 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -24,7 +24,6 @@ import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityClientController; import android.app.IActivityController; -import android.app.IAppTask; import android.app.IAssistDataReceiver; import android.app.IInstrumentationWatcher; import android.app.IProcessObserver; @@ -107,14 +106,6 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - - /** - * The DreamActivity has to be started in a special way that does not involve the PackageParser. - * The DreamActivity is a framework component inserted in the dream application process. Hence, - * it is not declared in the application's manifest and cannot be parsed. startDreamActivity - * creates the activity and starts it without reaching out to the PackageParser. - */ - boolean startDreamActivity(in Intent intent); int startActivityIntentSender(in IApplicationThread caller, in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5e7f5d62e256f..9b19937444bdd 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -26,7 +26,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.Activity; -import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -1268,9 +1267,7 @@ public class DreamService extends Service implements Window.Callback { fetchDreamLabel(this, serviceInfo, isPreviewMode)); try { - if (!ActivityTaskManager.getService().startDreamActivity(i)) { - detach(); - } + mDreamManager.startDreamActivity(i); } catch (SecurityException e) { Log.w(mTag, "Received SecurityException trying to start DreamActivity. " diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 609425c95b0d4..dd8b3deabc019 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -17,6 +17,7 @@ package android.service.dreams; import android.content.ComponentName; +import android.content.Intent; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.IBinder; @@ -45,4 +46,5 @@ interface IDreamManager { void setDreamComponentsForUser(int userId, in ComponentName[] componentNames); void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); + void startDreamActivity(in Intent intent); } diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84e..633bf73120e14 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -16,11 +16,11 @@ package com.android.server.dreams; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; +import android.app.IAppTask; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -213,6 +213,27 @@ final class DreamController { } } + /** + * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller + * can stop the dream task when necessary. + */ + void setDreamAppTask(Binder dreamToken, IAppTask appTask) { + if (mCurrentDream == null || mCurrentDream.mToken != dreamToken + || mCurrentDream.mAppTask != null) { + Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = " + + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken + + ". Ending this dream activity."); + try { + appTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop illegal dream activity."); + } + return; + } + + mCurrentDream.mAppTask = appTask; + } + /** * Stops dreaming. * @@ -303,8 +324,14 @@ final class DreamController { mSentStartBroadcast = false; } - mActivityTaskManager.removeRootTasksWithActivityTypes( - new int[] {ACTIVITY_TYPE_DREAM}); + if (mCurrentDream != null && mCurrentDream.mAppTask != null) { + // Finish the dream task in case it hasn't finished by itself already. + try { + mCurrentDream.mAppTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop dream activity."); + } + } mListener.onDreamStopped(dream.mToken); } @@ -364,6 +391,7 @@ final class DreamController { public final boolean mIsPreviewMode; public final boolean mCanDoze; public final int mUserId; + public IAppTask mAppTask; public PowerManager.WakeLock mWakeLock; public boolean mBound; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 68cf59f0ae1f1..d88fe8a6c201f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -27,6 +27,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IAppTask; import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -37,6 +38,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; @@ -116,6 +118,7 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final PackageManagerInternal mPmInternal; private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; @@ -216,6 +219,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); + mPmInternal = getLocalService(PackageManagerInternal.class); mUserManager = context.getSystemService(UserManager.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); @@ -1064,6 +1068,64 @@ public final class DreamManagerService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + @Override // Binder call + public void startDreamActivity(@NonNull Intent intent) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + // We post here, because startDreamActivity and setDreamAppTask have to run + // synchronously and DreamController#setDreamAppTask has to run on mHandler. + mHandler.post(() -> { + final Binder dreamToken; + final String dreamPackageName; + synchronized (mLock) { + if (mCurrentDream == null) { + Slog.e(TAG, "Attempt to start DreamActivity, but the device is not " + + "dreaming. Aborting without starting the DreamActivity."); + return; + } + dreamToken = mCurrentDream.token; + dreamPackageName = mCurrentDream.name.getPackageName(); + } + + if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(), + callingUid)) { + Slog.e(TAG, "The dream activity can be started only when the device is dreaming" + + " and only by the active dream package."); + return; + } + + final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid, + callingPid); + if (appTask == null) { + Slog.e(TAG, "Could not start dream activity."); + stopDreamInternal(true, "DreamActivity not started"); + return; + } + mController.setDreamAppTask(dreamToken, appTask); + }); + } + + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, + int callingUid) { + if (dreamPackageName == null || packageName == null) { + Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= " + + dreamPackageName + ", packageName=" + packageName); + return false; + } + if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) { + Slog.e(TAG, "Cannot launch dream activity because package=" + + packageName + " does not match callingUid=" + callingUid); + return false; + } + if (packageName.equals(dreamPackageName)) { + return true; + } + Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName + + " does not match " + dreamPackageName); + return false; + } + } private final class LocalService extends DreamManagerInternal { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 32f1f42aacb94..a2547fd437d18 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.BackgroundStartPrivileges; import android.app.IActivityManager; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.ITaskStackListener; import android.app.ProfilerInfo; @@ -307,6 +308,12 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent); + /** + * Starts a dream activity in the DreamService's process. + */ + public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, + int callingPid); + /** * Set a uid that is allowed to bypass stopped app switches, launching an app * whenever it wants. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 4a658d6abaf41..ea5b87b95cd48 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1466,23 +1466,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return false; } - private void enforceCallerIsDream(String callerPackageName) { - final long origId = Binder.clearCallingIdentity(); - try { - if (!canLaunchDreamActivity(callerPackageName)) { - throw new SecurityException("The dream activity can be started only when the device" - + " is dreaming and only by the active dream package."); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public boolean startDreamActivity(@NonNull Intent intent) { - assertPackageMatchesCallingUid(intent.getPackage()); - enforceCallerIsDream(intent.getPackage()); - + private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid, + int callingPid) { final ActivityInfo a = new ActivityInfo(); a.theme = com.android.internal.R.style.Theme_Dream; a.exported = true; @@ -1500,7 +1485,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); synchronized (mGlobalLock) { - final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + final WindowProcessController process = mProcessMap.getProcess(callingPid); a.packageName = process.mInfo.packageName; a.applicationInfo = process.mInfo; @@ -1508,26 +1493,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.uiOptions = process.mInfo.uiOptions; a.taskAffinity = "android:" + a.packageName + "/dream"; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final long origId = Binder.clearCallingIdentity(); - try { - getActivityStartController().obtainStarter(intent, "dream") - .setCallingUid(callingUid) - .setCallingPid(callingPid) - .setCallingPackage(intent.getPackage()) - .setActivityInfo(a) - .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) - // To start the dream from background, we need to start it from a persistent - // system process. Here we set the real calling uid to the system server uid - .setRealCallingUid(Binder.getCallingUid()) - .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) - .execute(); - return true; - } finally { - Binder.restoreCallingIdentity(origId); - } + final ActivityRecord[] outActivity = new ActivityRecord[1]; + getActivityStartController().obtainStarter(intent, "dream") + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setCallingPackage(intent.getPackage()) + .setActivityInfo(a) + .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) + .setOutActivity(outActivity) + // To start the dream from background, we need to start it from a persistent + // system process. Here we set the real calling uid to the system server uid + .setRealCallingUid(Binder.getCallingUid()) + .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) + .execute(); + + final ActivityRecord started = outActivity[0]; + final IAppTask appTask = started == null ? null : + new AppTaskImpl(this, started.getTask().mTaskId, callingUid); + return appTask; } } @@ -5924,6 +5908,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) { + return startDreamActivityInternal(intent, callingUid, callingPid); + } + @Override public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {