blob: e02e8671f2111ac12b2d7c641c73217021d09f83 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
import static com.android.server.wm.Task.ActivityState.DESTROYED;
import static com.android.server.wm.Task.ActivityState.DESTROYING;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityClientController;
import android.app.IRequestFinishCallback;
import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
import android.app.servertransaction.PipStateTransactionItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
import android.window.SizeConfigurationBuckets;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.uri.NeededUriGrants;
import com.android.server.vr.VrManagerInternal;
/**
* Server side implementation for the client activity to interact with system.
*
* @see android.app.ActivityClient
*/
class ActivityClientController extends IActivityClientController.Stub {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityClientController" : TAG_ATM;
private final ActivityTaskManagerService mService;
private final WindowManagerGlobalLock mGlobalLock;
private final ActivityTaskSupervisor mTaskSupervisor;
private final Context mContext;
/** Wrapper around VoiceInteractionServiceManager. */
private AssistUtils mAssistUtils;
ActivityClientController(ActivityTaskManagerService service) {
mService = service;
mGlobalLock = service.mGlobalLock;
mTaskSupervisor = service.mTaskSupervisor;
mContext = service.mContext;
}
void onSystemReady() {
mAssistUtils = new AssistUtils(mContext);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(
"ActivityClientController", e);
}
}
@Override
public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
return;
}
mTaskSupervisor.activityIdleInternal(r, false /* fromTimeout */,
false /* processPausingActivities */, config);
if (stopProfiling && r.hasProcess()) {
r.app.clearProfilerIfNeeded();
}
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
}
Binder.restoreCallingIdentity(origId);
}
@Override
public void activityTopResumedStateLost() {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
mTaskSupervisor.handleTopResumedStateReleased(false /* timeout */);
}
Binder.restoreCallingIdentity(origId);
}
@Override
public void activityPaused(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityPaused");
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
r.activityPaused(false);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
Binder.restoreCallingIdentity(origId);
}
@Override
public void activityStopped(IBinder token, Bundle icicle, PersistableBundle persistentState,
CharSequence description) {
if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token);
// Refuse possible leaked file descriptors.
if (icicle != null && icicle.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
final long origId = Binder.clearCallingIdentity();
String restartingName = null;
int restartingUid = 0;
final ActivityRecord r;
synchronized (mGlobalLock) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
if (r.attachedToProcess() && r.isState(Task.ActivityState.RESTARTING_PROCESS)) {
// The activity was requested to restart from
// {@link #restartActivityProcessIfVisible}.
restartingName = r.app.mName;
restartingUid = r.app.mUid;
}
r.activityStopped(icicle, persistentState, description);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (restartingName != null) {
// In order to let the foreground activity can be restarted with its saved state from
// {@link android.app.Activity#onSaveInstanceState}, the kill operation is postponed
// until the activity reports stopped with the state. And the activity record will be
// kept because the record state is restarting, then the activity will be restarted
// immediately if it is still the top one.
mTaskSupervisor.removeRestartTimeouts(r);
mService.mAmInternal.killProcess(restartingName, restartingUid,
"restartActivityProcess");
}
mService.mAmInternal.trimApplications();
Binder.restoreCallingIdentity(origId);
}
@Override
public void activityDestroyed(IBinder token) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityDestroyed");
try {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
r.destroyed("activityDestroyed");
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
}
@Override
public void activityRelaunched(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
r.finishRelaunching();
}
}
Binder.restoreCallingIdentity(origId);
}
@Override
public void reportSizeConfigurations(IBinder token,
SizeConfigurationBuckets sizeConfigurations) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Report configuration: %s %s",
token, sizeConfigurations);
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setSizeConfigurations(sizeConfigurations);
}
}
}
/**
* Attempts to move a task backwards in z-order (the order of activities within the task is
* unchanged).
*
* There are several possible results of this call:
* - if the task is locked, then we will show the lock toast.
* - if there is a task behind the provided task, then that task is made visible and resumed as
* this task is moved to the back.
* - otherwise, if there are no other tasks in the root task:
* - if this task is in the pinned mode, then we remove the task completely, which will
* have the effect of moving the task to the top or bottom of the fullscreen root task
* (depending on whether it is visible).
* - otherwise, we simply return home and hide this task.
*
* @param token A reference to the activity we wish to move.
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in a task.
* @return Returns true if the move completed, false if not.
*/
@Override
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) {
enforceNotIsolatedCaller("moveActivityTaskToBack");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot);
final Task task = mService.mRootWindowContainer.anyTaskForId(taskId);
if (task != null) {
return ActivityRecord.getRootTask(token).moveTaskToBack(task);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
return false;
}
@Override
public boolean shouldUpRecreateTask(IBinder token, String destAffinity) {
synchronized (mGlobalLock) {
final ActivityRecord srec = ActivityRecord.forTokenLocked(token);
if (srec != null) {
return srec.getRootTask().shouldUpRecreateTaskLocked(srec, destAffinity);
}
}
return false;
}
@Override
public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
Intent resultData) {
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
return false;
}
}
// Carefully collect grants without holding lock.
final NeededUriGrants destGrants = mService.collectGrants(destIntent, r);
final NeededUriGrants resultGrants = mService.collectGrants(resultData, r.resultTo);
synchronized (mGlobalLock) {
return r.getRootTask().navigateUpTo(
r, destIntent, destGrants, resultCode, resultData, resultGrants);
}
}
@Override
public boolean releaseActivityInstance(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null || !r.isDestroyable()) {
return false;
}
r.destroyImmediately("app-req");
return r.isState(DESTROYING, DESTROYED);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
/**
* This is the internal entry point for handling Activity.finish().
*
* @param token The Binder token referencing the Activity we want to finish.
* @param resultCode Result code, if any, from this Activity.
* @param resultData Result data (Intent), if any, from this Activity.
* @param finishTask Whether to finish the task associated with this Activity.
* @return Returns true if the activity successfully finished, or false if it is still running.
*/
@Override
public boolean finishActivity(IBinder token, int resultCode, Intent resultData,
int finishTask) {
// Refuse possible leaked file descriptors.
if (resultData != null && resultData.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
return true;
}
}
// Carefully collect grants without holding lock.
final NeededUriGrants resultGrants = mService.collectGrants(resultData, r.resultTo);
synchronized (mGlobalLock) {
// Check again in case activity was removed when collecting grants.
if (!r.isInHistory()) {
return true;
}
// Keep track of the root activity of the task before we finish it.
final Task tr = r.getTask();
final ActivityRecord rootR = tr.getRootActivity();
if (rootR == null) {
Slog.w(TAG, "Finishing task with all activities already finished");
}
// Do not allow task to finish if last task in lockTask mode. Launchable priv-apps can
// finish.
if (mService.getLockTaskController().activityBlockedFromFinish(r)) {
return false;
}
// TODO: There is a dup. of this block of code in ActivityStack.navigateUpToLocked
// We should consolidate.
if (mService.mController != null) {
// Find the first activity that is not finishing.
final ActivityRecord next =
r.getRootTask().topRunningActivity(token, INVALID_TASK_ID);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
try {
resumeOK = mService.mController.activityResuming(next.packageName);
} catch (RemoteException e) {
mService.mController = null;
Watchdog.getInstance().setActivityController(null);
}
if (!resumeOK) {
Slog.i(TAG, "Not finishing activity because controller resumed");
return false;
}
}
}
// Note down that the process has finished an activity and is in background activity
// starts grace period.
if (r.app != null) {
r.app.setLastActivityFinishTimeIfNeeded(SystemClock.uptimeMillis());
}
final long origId = Binder.clearCallingIdentity();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
try {
final boolean res;
final boolean finishWithRootActivity =
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
// If requested, remove the task that is associated to this activity only if it
// was the root activity in the task. The result code and data is ignored
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
mTaskSupervisor.removeTask(tr, false /*killProcess*/,
finishWithRootActivity, "finish-activity");
res = true;
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
} else {
r.finishIfPossible(resultCode, resultData, resultGrants,
"app-request", true /* oomAdj */);
res = r.finishing;
if (!res) {
Slog.i(TAG, "Failed to finish by app-request");
}
}
return res;
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
}
@Override
public boolean finishActivityAffinity(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
return false;
}
// Do not allow task to finish if last task in lockTask mode. Launchable priv-apps
// can finish.
if (mService.getLockTaskController().activityBlockedFromFinish(r)) {
return false;
}
r.getTask().forAllActivities(activity -> r.finishIfSameAffinity(activity),
r /* boundary */, true /* includeBoundary */,
true /* traverseTopToBottom */);
return true;
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void finishSubActivity(IBinder token, String resultWho, int requestCode) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
// TODO: This should probably only loop over the task since you need to be in the
// same task to return results.
r.getRootTask().forAllActivities(activity -> {
activity.finishIfSubActivity(r /* parent */, resultWho, requestCode);
}, true /* traverseTopToBottom */);
mService.updateOomAdj();
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public boolean isTopOfTask(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
return r != null && r.getTask().getTopNonFinishingActivity() == r;
}
}
@Override
public boolean willActivityBeVisible(IBinder token) {
synchronized (mGlobalLock) {
final Task rootTask = ActivityRecord.getRootTask(token);
return rootTask != null && rootTask.willActivityBeVisible(token);
}
}
@Override
public int getDisplayId(IBinder activityToken) {
synchronized (mGlobalLock) {
final Task rootTask = ActivityRecord.getRootTask(activityToken);
if (rootTask != null) {
final int displayId = rootTask.getDisplayId();
return displayId != INVALID_DISPLAY ? displayId : DEFAULT_DISPLAY;
}
return DEFAULT_DISPLAY;
}
}
@Override
public int getTaskForActivity(IBinder token, boolean onlyRoot) {
synchronized (mGlobalLock) {
return ActivityRecord.getTaskForActivityLocked(token, onlyRoot);
}
}
@Override
public ComponentName getCallingActivity(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = getCallingRecord(token);
return r != null ? r.intent.getComponent() : null;
}
}
@Override
public String getCallingPackage(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = getCallingRecord(token);
return r != null ? r.info.packageName : null;
}
}
private static ActivityRecord getCallingRecord(IBinder token) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
return r != null ? r.resultTo : null;
}
@Override
public int getLaunchedFromUid(IBinder token) {
if (!canGetLaunchedFrom()) {
return INVALID_UID;
}
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return r != null ? r.launchedFromUid : INVALID_UID;
}
}
@Override
public String getLaunchedFromPackage(IBinder token) {
if (!canGetLaunchedFrom()) {
return null;
}
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return r != null ? r.launchedFromPackage : null;
}
}
/** Whether the caller can get the package or uid that launched its activity. */
private boolean canGetLaunchedFrom() {
final int uid = Binder.getCallingUid();
if (UserHandle.getAppId(uid) == SYSTEM_UID) {
return true;
}
final PackageManagerInternal pm = mService.mWindowManager.mPmInternal;
final AndroidPackage callingPkg = pm.getPackage(uid);
if (callingPkg == null) {
return false;
}
if (callingPkg.isSignedWithPlatformKey()) {
return true;
}
final String[] installerNames = pm.getKnownPackageNames(
PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.getUserId(uid));
return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]);
}
@Override
public void setRequestedOrientation(IBinder token, int requestedOrientation) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setRequestedOrientation(requestedOrientation);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public int getRequestedOrientation(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
return r != null
? r.getRequestedOrientation() : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
}
@Override
public boolean convertFromTranslucent(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
return r != null && r.setOccludesParent(true);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public boolean convertToTranslucent(IBinder token, Bundle options) {
final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options);
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
return false;
}
final ActivityRecord under = r.getTask().getActivityBelow(r);
if (under != null) {
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
return r.setOccludesParent(false);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public boolean isImmersive(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
throw new IllegalArgumentException();
}
return r.immersive;
}
}
@Override
public void setImmersive(IBinder token, boolean immersive) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) {
throw new IllegalArgumentException();
}
r.immersive = immersive;
// Update associated state if we're frontmost.
if (r.isFocusedActivityOnDisplay()) {
ProtoLog.d(WM_DEBUG_IMMERSIVE, "Frontmost changed immersion: %s", r);
mService.applyUpdateLockStateLocked(r);
}
}
}
@Override
public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"enterPictureInPictureMode", token, params);
return mService.enterPictureInPictureMode(r, params);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void setPictureInPictureParams(IBinder token, final PictureInPictureParams params) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"setPictureInPictureParams", token, params);
// Only update the saved args from the args that are set.
r.setPictureInPictureParams(params);
if (r.inPinnedWindowingMode()) {
// If the activity is already in picture-in-picture, update the pinned task now
// if it is not already expanding to fullscreen. Otherwise, the arguments will
// be used the next time the activity enters PiP.
final Task rootTask = r.getRootTask();
rootTask.setPictureInPictureAspectRatio(
r.pictureInPictureArgs.getAspectRatio());
rootTask.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
/**
* Splash screen view is attached to activity.
*/
@Override
public void splashScreenAttached(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
ActivityRecord.splashScreenAttachedLocked(token);
}
Binder.restoreCallingIdentity(origId);
}
/**
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
*
* @return the activity record for the given {@param token} if all the checks pass.
*/
private ActivityRecord ensureValidPictureInPictureActivityParams(String caller,
IBinder token, PictureInPictureParams params) {
if (!mService.mSupportsPictureInPicture) {
throw new IllegalStateException(caller
+ ": Device doesn't support picture-in-picture mode.");
}
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
throw new IllegalStateException(caller
+ ": Can't find activity for token=" + token);
}
if (!r.supportsPictureInPicture()) {
throw new IllegalStateException(caller
+ ": Current activity does not support picture-in-picture.");
}
if (params.hasSetAspectRatio()
&& !mService.mWindowManager.isValidPictureInPictureAspectRatio(
r.mDisplayContent, params.getAspectRatio())) {
final float minAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
final float maxAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
throw new IllegalArgumentException(String.format(caller
+ ": Aspect ratio is too extreme (must be between %f and %f).",
minAspectRatio, maxAspectRatio));
}
// Truncate the number of actions if necessary.
params.truncateActions(ActivityTaskManager.getMaxNumPictureInPictureActions(mContext));
return r;
}
/**
* Requests that an activity should enter picture-in-picture mode if possible. This method may
* be used by the implementation of non-phone form factors.
*/
void requestPictureInPictureMode(@NonNull ActivityRecord r) {
if (r.inPinnedWindowingMode()) {
throw new IllegalStateException("Activity is already in PIP mode");
}
final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
"requestPictureInPictureMode", /* beforeStopping */ false);
if (!canEnterPictureInPicture) {
throw new IllegalStateException(
"Requested PIP on an activity that doesn't support it");
}
if (r.pictureInPictureArgs.isAutoEnterEnabled()) {
mService.enterPictureInPictureMode(r, r.pictureInPictureArgs);
return;
}
try {
final ClientTransaction transaction = ClientTransaction.obtain(
r.app.getThread(), r.token);
transaction.addCallback(EnterPipRequestedItem.obtain());
mService.getLifecycleManager().scheduleTransaction(transaction);
} catch (Exception e) {
Slog.w(TAG, "Failed to send enter pip requested item: "
+ r.intent.getComponent(), e);
}
}
/**
* Alert the client that the Picture-in-Picture state has changed.
*/
void onPictureInPictureStateChanged(@NonNull ActivityRecord r,
PictureInPictureUiState pipState) {
if (!r.inPinnedWindowingMode()) {
throw new IllegalStateException("Activity is not in PIP mode");
}
try {
final ClientTransaction transaction = ClientTransaction.obtain(
r.app.getThread(), r.token);
transaction.addCallback(PipStateTransactionItem.obtain(pipState));
mService.getLifecycleManager().scheduleTransaction(transaction);
} catch (Exception e) {
Slog.w(TAG, "Failed to send pip state transaction item: "
+ r.intent.getComponent(), e);
}
}
@Override
public void toggleFreeformWindowingMode(IBinder token) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
throw new IllegalArgumentException(
"toggleFreeformWindowingMode: No activity record matching token="
+ token);
}
final Task rootTask = r.getRootTask();
if (rootTask == null) {
throw new IllegalStateException("toggleFreeformWindowingMode: the activity "
+ "doesn't have a root task");
}
if (!rootTask.inFreeformWindowingMode()
&& rootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
throw new IllegalStateException("toggleFreeformWindowingMode: You can only "
+ "toggle between fullscreen and freeform.");
}
if (rootTask.inFreeformWindowingMode()) {
rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
} else if (!r.supportsFreeform()) {
throw new IllegalStateException(
"This activity is currently not freeform-enabled");
} else if (rootTask.getParent().inFreeformWindowingMode()) {
// If the window is on a freeform display, set it to undefined. It will be
// resolved to freeform and it can adjust windowing mode when the display mode
// changes in runtime.
rootTask.setWindowingMode(WINDOWING_MODE_UNDEFINED);
} else {
rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void startLockTaskModeByToken(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
mService.startLockTaskMode(r.getTask(), false /* isSystemCaller */);
}
}
}
@Override
public void stopLockTaskModeByToken(IBinder token) {
mService.stopLockTaskModeInternal(token, false /* isSystemCaller */);
}
@Override
public void showLockTaskEscapeMessage(IBinder token) {
synchronized (mGlobalLock) {
if (ActivityRecord.forTokenLocked(token) != null) {
mService.getLockTaskController().showLockTaskToast();
}
}
}
@Override
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription td) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setTaskDescription(td);
}
}
}
@Override
public boolean showAssistFromActivity(IBinder token, Bundle args) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord caller = ActivityRecord.forTokenLocked(token);
final Task topRootTask = mService.getTopDisplayFocusedRootTask();
final ActivityRecord top = topRootTask != null
? topRootTask.getTopNonFinishingActivity() : null;
if (top != caller) {
Slog.w(TAG, "showAssistFromActivity failed: caller " + caller
+ " is not current top " + top);
return false;
}
if (!top.nowVisible) {
Slog.w(TAG, "showAssistFromActivity failed: caller " + caller
+ " is not visible");
return false;
}
}
return mAssistUtils.showSessionForActiveService(args, SHOW_SOURCE_APPLICATION,
null /* showCallback */, token);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public boolean isRootVoiceInteraction(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
return r != null && r.rootVoiceInteraction;
}
}
@Override
public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
Slog.i(TAG, "Activity tried to startLocalVoiceInteraction");
synchronized (mGlobalLock) {
final Task topRootTask = mService.getTopDisplayFocusedRootTask();
final ActivityRecord activity = topRootTask != null
? topRootTask.getTopNonFinishingActivity() : null;
if (ActivityRecord.forTokenLocked(callingActivity) != activity) {
throw new SecurityException("Only focused activity can call startVoiceInteraction");
}
if (mService.mRunningVoice != null || activity.getTask().voiceSession != null
|| activity.voiceSession != null) {
Slog.w(TAG, "Already in a voice interaction, cannot start new voice interaction");
return;
}
if (activity.pendingVoiceInteractionStart) {
Slog.w(TAG, "Pending start of voice interaction already.");
return;
}
activity.pendingVoiceInteractionStart = true;
}
LocalServices.getService(VoiceInteractionManagerInternal.class)
.startLocalVoiceInteraction(callingActivity, options);
}
@Override
public void stopLocalVoiceInteraction(IBinder callingActivity) {
LocalServices.getService(VoiceInteractionManagerInternal.class)
.stopLocalVoiceInteraction(callingActivity);
}
@Override
public void setShowWhenLocked(IBinder token, boolean showWhenLocked) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setShowWhenLocked(showWhenLocked);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void setInheritShowWhenLocked(IBinder token, boolean inheritShowWhenLocked) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setInheritShowWhenLocked(inheritShowWhenLocked);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void setTurnScreenOn(IBinder token, boolean turnScreenOn) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setTurnScreenOn(turnScreenOn);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void reportActivityFullyDrawn(IBinder token, boolean restoredFromBundle) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.reportFullyDrawnLocked(restoredFromBundle);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void overridePendingTransition(IBinder token, String packageName,
int enterAnim, int exitAnim) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) {
r.mDisplayContent.mAppTransition.overridePendingAppTransition(
packageName, enterAnim, exitAnim, null, null,
r.mOverrideTaskTransition);
}
}
Binder.restoreCallingIdentity(origId);
}
@Override
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) {
mService.enforceSystemHasVrFeature();
final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
}
if (r == null) {
throw new IllegalArgumentException();
}
final int err;
if ((err = vrService.hasVrPackage(packageName, r.mUserId)) != VrManagerInternal.NO_ERROR) {
return err;
}
// Clear the binder calling uid since this path may call moveToTask().
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
r.requestedVrComponent = (enabled) ? packageName : null;
// Update associated state if this activity is currently focused.
if (r.isFocusedActivityOnDisplay()) {
mService.applyUpdateVrModeLocked(r);
}
return 0;
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
@Override
public void setDisablePreviewScreenshots(IBinder token, boolean disable) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.setDisablePreviewScreenshots(disable);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
void restartActivityProcessIfVisible(IBinder token) {
ActivityTaskManagerService.enforceTaskPermission("restartActivityProcess");
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.restartProcessIfVisible();
}
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
@Override
public void invalidateHomeTaskSnapshot(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null && r.isActivityTypeHome()) {
mService.mWindowManager.mTaskSnapshotController.removeSnapshotCache(
r.getTask().mTaskId);
}
}
}
@Override
public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
CharSequence message) {
if (message != null) {
mService.mAmInternal.enforceCallingPermission(
android.Manifest.permission.SHOW_KEYGUARD_MESSAGE, "dismissKeyguard");
}
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
mService.mKeyguardController.dismissKeyguard(token, callback, message);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
@Override
public void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) {
mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"registerRemoteAnimations");
definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.registerRemoteAnimations(definition);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void unregisterRemoteAnimations(IBinder token) {
mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"unregisterRemoteAnimations");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
r.unregisterRemoteAnimations();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
final long origId = Binder.clearCallingIdentity();
try {
final Intent baseActivityIntent;
final boolean launchedFromHome;
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
if (mService.mWindowOrganizerController.mTaskOrganizerController
.handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
// This task is handled by a task organizer that has requested the back pressed
// callback.
return;
}
final Intent baseIntent = r.getTask().getBaseIntent();
final boolean activityIsBaseActivity = baseIntent != null
&& r.mActivityComponent.equals(baseIntent.getComponent());
baseActivityIntent = activityIsBaseActivity ? r.intent : null;
launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
// If the activity is one of the main entry points for the application, then we should
// refrain from finishing the activity and instead move it to the back to keep it in
// memory. The requirements for this are:
// 1. The current activity is the base activity for the task.
// 2. a. If the activity was launched by the home process, we trust that its intent
// was resolved, so we check if the it is a main intent for the application.
// b. Otherwise, we query Package Manager to verify whether the activity is a
// launcher activity for the application.
if (baseActivityIntent != null
&& ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
|| isLauncherActivity(baseActivityIntent.getComponent()))) {
moveActivityTaskToBack(token, false /* nonRoot */);
return;
}
// The default option for handling the back button is to finish the Activity.
try {
callback.requestFinish();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to invoke request finish callback", e);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
/**
* Queries PackageManager to see if the given activity is one of the main entry point for the
* application. This should not be called with the WM lock held.
*/
@SuppressWarnings("unchecked")
private boolean isLauncherActivity(@NonNull ComponentName activity) {
final Intent queryIntent = new Intent(Intent.ACTION_MAIN);
queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
queryIntent.setPackage(activity.getPackageName());
try {
final ParceledListSlice<ResolveInfo> resolved =
mService.getPackageManager().queryIntentActivities(
queryIntent, null, 0, mContext.getUserId());
if (resolved == null) return false;
for (final ResolveInfo ri : resolved.getList()) {
if (ri.getComponentInfo().getComponentName().equals(activity)) {
return true;
}
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to query intent activities", e);
}
return false;
}
}