| /* |
| * 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.Activity.FULLSCREEN_MODE_REQUEST_ENTER; |
| import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; |
| import static android.app.ActivityTaskManager.INVALID_TASK_ID; |
| import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE; |
| import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY; |
| import static android.app.FullscreenRequestHandler.RESULT_APPROVED; |
| import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_DEFAULT_FREEFORM; |
| import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FREEFORM; |
| import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY; |
| import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED; |
| 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 android.view.WindowManager.TRANSIT_CHANGE; |
| import static android.view.WindowManager.TRANSIT_TO_BACK; |
| import static android.view.WindowManager.TRANSIT_TO_FRONT; |
| |
| 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.ActivityRecord.State.DESTROYED; |
| import static com.android.server.wm.ActivityRecord.State.DESTROYING; |
| import static com.android.server.wm.ActivityRecord.State.PAUSING; |
| import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; |
| import static com.android.server.wm.ActivityRecord.State.RESUMED; |
| import static com.android.server.wm.ActivityRecord.State.STOPPING; |
| 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 android.Manifest; |
| import android.annotation.ColorInt; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.FullscreenRequestHandler; |
| import android.app.IActivityClientController; |
| import android.app.ICompatCameraControlCallback; |
| import android.app.IRequestFinishCallback; |
| import android.app.PictureInPictureParams; |
| import android.app.PictureInPictureUiState; |
| import android.app.compat.CompatChanges; |
| import android.app.servertransaction.EnterPipRequestedItem; |
| import android.app.servertransaction.PipStateTransactionItem; |
| import android.compat.annotation.ChangeId; |
| 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.res.Configuration; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.IRemoteCallback; |
| 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 android.window.TransitionInfo; |
| |
| 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.KnownPackages; |
| import com.android.server.pm.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; |
| |
| /** |
| * Grants access to the launching app's identity if the app opted-in to sharing its identity |
| * by launching this activity with an instance of {@link android.app.ActivityOptions} on which |
| * {@link android.app.ActivityOptions#setShareIdentityEnabled(boolean)} was invoked with a |
| * value of {@code true}, or if the launched activity's uid is the same as the launching |
| * app's. When this change is enabled and one of these requirements is met, the activity |
| * can access the launching app's uid and package name with {@link |
| * android.app.Activity#getLaunchedFromUid()} and {@link |
| * android.app.Activity#getLaunchedFromPackage()}, respectively. |
| */ |
| @ChangeId |
| public static final long ACCESS_SHARED_IDENTITY = 259743961L; |
| |
| 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 activityRefreshed(IBinder token) { |
| final long origId = Binder.clearCallingIdentity(); |
| synchronized (mGlobalLock) { |
| ActivityRecord.activityRefreshedLocked(token); |
| } |
| 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.isState(STOPPING, RESTARTING_PROCESS) |
| && mTaskSupervisor.hasScheduledRestartTimeouts(r)) { |
| // Recover the restarting state which was replaced by other lifecycle changes. |
| r.setState(RESTARTING_PROCESS, "continue-restart"); |
| } |
| if (r.attachedToProcess() && r.isState(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 activityLocalRelaunch(IBinder token) { |
| final long origId = Binder.clearCallingIdentity(); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| if (r != null) { |
| r.startRelaunching(); |
| } |
| } |
| 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, String resolvedType, |
| 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, resolvedType, 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; |
| mTaskSupervisor.getBackgroundActivityLaunchController() |
| .onActivityRequestedFinishing(r); |
| 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", r.getUid(), r.info.name); |
| 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 void setForceSendResultForMediaProjection(IBinder token) { |
| // Require that this is invoked only during MediaProjection setup. |
| mService.mAmInternal.enforceCallingPermission( |
| Manifest.permission.MANAGE_MEDIA_PROJECTION, |
| "setForceSendResultForMediaProjection"); |
| |
| final ActivityRecord r; |
| synchronized (mGlobalLock) { |
| r = ActivityRecord.isInRootTaskLocked(token); |
| if (r == null || !r.isInHistory()) { |
| return; |
| } |
| r.setForceSendResultForMediaProjection(); |
| } |
| } |
| |
| @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) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| if (r == null) { |
| return INVALID_TASK_ID; |
| } |
| final Task task = r.getTask(); |
| if (onlyRoot) { |
| return task.getRootActivity() == r ? task.mTaskId : INVALID_TASK_ID; |
| } |
| return task.mTaskId; |
| } |
| } |
| |
| /** |
| * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if |
| * the task {@link Configuration} cannot be obtained. |
| */ |
| @Override |
| @Nullable |
| public Configuration getTaskConfiguration(IBinder activityToken) { |
| synchronized (mGlobalLock) { |
| final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken); |
| if (ar == null) { |
| return null; |
| } |
| return ar.getTask().getConfiguration(); |
| } |
| } |
| |
| @Override |
| @Nullable |
| public IBinder getActivityTokenBelow(IBinder activityToken) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken); |
| if (ar == null) { |
| return null; |
| } |
| // Exclude finishing activity. |
| final ActivityRecord below = ar.getTask().getActivity((r) -> !r.finishing, |
| ar, false /*includeBoundary*/, true /*traverseTopToBottom*/); |
| if (below != null && below.getUid() == ar.getUid()) { |
| return below.token; |
| } |
| } |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| return null; |
| } |
| |
| @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) { |
| final int uid = Binder.getCallingUid(); |
| final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r))) { |
| return r.launchedFromUid; |
| } |
| } |
| return INVALID_UID; |
| } |
| |
| @Override |
| public String getLaunchedFromPackage(IBinder token) { |
| final int uid = Binder.getCallingUid(); |
| final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r))) { |
| return r.launchedFromPackage; |
| } |
| } |
| return null; |
| } |
| |
| /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */ |
| private boolean isInternalCallerGetLaunchedFrom(int uid) { |
| 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( |
| KnownPackages.PACKAGE_INSTALLER, UserHandle.getUserId(uid)); |
| return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]); |
| } |
| |
| /** |
| * Returns whether the specified {@code uid} can access the launching app's identity by |
| * verifying whether the provided {@code ActivityRecord r} has opted in to sharing its |
| * identity or if the uid of the activity matches that of the launching app. |
| */ |
| private static boolean canGetLaunchedFromLocked(int uid, ActivityRecord r) { |
| if (CompatChanges.isChangeEnabled(ACCESS_SHARED_IDENTITY, uid)) { |
| return r.mShareIdentity || r.launchedFromUid == uid; |
| } |
| return false; |
| } |
| |
| @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) { |
| EventLogTags.writeWmSetRequestedOrientation(requestedOrientation, |
| r.shortComponentName); |
| 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.getOverrideOrientation() |
| : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; |
| } |
| } |
| |
| @Override |
| public boolean convertFromTranslucent(IBinder token) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| // Create a transition if the activity is playing in case the below activity didn't |
| // commit invisible. That's because if any activity below this one has changed its |
| // visibility while playing transition, there won't able to commit visibility until |
| // the running transition finish. |
| final Transition transition = r != null |
| && r.mTransitionController.inPlayingTransition(r) |
| && !r.mTransitionController.isCollecting() |
| ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null; |
| final boolean changed = r != null && r.setOccludesParent(true); |
| if (transition != null) { |
| if (changed) { |
| r.mTransitionController.requestStartTransition(transition, |
| null /*startTask */, null /* remoteTransition */, |
| null /* displayChange */); |
| r.mTransitionController.setReady(r.getDisplayContent()); |
| } else { |
| transition.abort(); |
| } |
| } |
| return changed; |
| } |
| } 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; |
| } |
| // Create a transition to make sure the activity change is collected. |
| final Transition transition = r.mTransitionController.isShellTransitionsEnabled() |
| && !r.mTransitionController.isCollecting() |
| ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null; |
| final boolean changed = r.setOccludesParent(false); |
| if (transition != null) { |
| if (changed) { |
| r.mTransitionController.requestStartTransition(transition, |
| null /*startTask */, null /* remoteTransition */, |
| null /* displayChange */); |
| r.mTransitionController.setReady(r.getDisplayContent()); |
| if (under != null && under.returningOptions != null |
| && under.returningOptions.getAnimationType() |
| == ANIM_SCENE_TRANSITION) { |
| // Pass along the scene-transition animation-type |
| transition.setOverrideAnimation(TransitionInfo.AnimationOptions |
| .makeSceneTransitionAnimOptions(), null, null); |
| } |
| } else { |
| transition.abort(); |
| } |
| } |
| return changed; |
| } |
| } 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, true /* fromClient */); |
| } |
| } 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); |
| r.setPictureInPictureParams(params); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(origId); |
| } |
| } |
| |
| @Override |
| public void setShouldDockBigOverlays(IBinder token, boolean shouldDockBigOverlays) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| r.setShouldDockBigOverlays(shouldDockBigOverlays); |
| } |
| } 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); |
| } |
| |
| @Override |
| public void requestCompatCameraControl(IBinder token, boolean showControl, |
| boolean transformationApplied, ICompatCameraControlCallback callback) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null) { |
| r.updateCameraCompatState(showControl, transformationApplied, callback); |
| } |
| } |
| } finally { |
| 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."); |
| } |
| |
| 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); |
| |
| if (params.hasSetAspectRatio() |
| && !mService.mWindowManager.isValidPictureInPictureAspectRatio( |
| r.mDisplayContent, params.getAspectRatioFloat())) { |
| throw new IllegalArgumentException(String.format(caller |
| + ": Aspect ratio is too extreme (must be between %f and %f).", |
| minAspectRatio, maxAspectRatio)); |
| } |
| |
| if (mService.mSupportsExpandedPictureInPicture && params.hasSetExpandedAspectRatio() |
| && !mService.mWindowManager.isValidExpandedPictureInPictureAspectRatio( |
| r.mDisplayContent, params.getExpandedAspectRatioFloat())) { |
| throw new IllegalArgumentException(String.format(caller |
| + ": Expanded aspect ratio is not extreme enough (must not 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. |
| * |
| * @return false if the activity cannot enter PIP mode. |
| */ |
| boolean requestPictureInPictureMode(@NonNull ActivityRecord r) { |
| if (r.inPinnedWindowingMode()) { |
| return false; |
| } |
| |
| final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState( |
| "requestPictureInPictureMode", /* beforeStopping */ false); |
| if (!canEnterPictureInPicture) { |
| return false; |
| } |
| |
| if (r.pictureInPictureArgs.isAutoEnterEnabled()) { |
| return mService.enterPictureInPictureMode(r, r.pictureInPictureArgs, |
| false /* fromClient */); |
| } |
| |
| try { |
| mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), |
| EnterPipRequestedItem.obtain(r.token)); |
| return true; |
| } catch (Exception e) { |
| Slog.w(TAG, "Failed to send enter pip requested item: " |
| + r.intent.getComponent(), e); |
| return false; |
| } |
| } |
| |
| /** |
| * 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 { |
| mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), |
| PipStateTransactionItem.obtain(r.token, pipState)); |
| } 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); |
| rootTask.setBounds(null); |
| } 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); |
| } |
| } |
| |
| private @FullscreenRequestHandler.RequestResult int validateMultiwindowFullscreenRequestLocked( |
| Task topFocusedRootTask, int fullscreenRequest, ActivityRecord requesterActivity) { |
| // If the mode is not by default freeform, the freeform will be a user-driven event. |
| if (topFocusedRootTask.getParent().getWindowingMode() != WINDOWING_MODE_FREEFORM) { |
| return RESULT_FAILED_NOT_DEFAULT_FREEFORM; |
| } |
| // If this is not coming from the currently top-most activity, reject the request. |
| if (requesterActivity != topFocusedRootTask.getTopMostActivity()) { |
| return RESULT_FAILED_NOT_TOP_FOCUSED; |
| } |
| if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) { |
| if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) { |
| return RESULT_FAILED_NOT_IN_FREEFORM; |
| } |
| } else { |
| if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { |
| return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY; |
| } |
| if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) { |
| return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY; |
| } |
| } |
| return RESULT_APPROVED; |
| } |
| |
| @Override |
| public void requestMultiwindowFullscreen(IBinder callingActivity, int fullscreenRequest, |
| IRemoteCallback callback) { |
| final long ident = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| requestMultiwindowFullscreenLocked(callingActivity, fullscreenRequest, callback); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(ident); |
| } |
| } |
| |
| private void requestMultiwindowFullscreenLocked(IBinder callingActivity, int fullscreenRequest, |
| IRemoteCallback callback) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(callingActivity); |
| if (r == null) { |
| return; |
| } |
| |
| // If the shell transition is not enabled, just execute and done. |
| final TransitionController controller = r.mTransitionController; |
| if (!controller.isShellTransitionsEnabled()) { |
| final @FullscreenRequestHandler.RequestResult int validateResult; |
| final Task topFocusedRootTask; |
| topFocusedRootTask = mService.getTopDisplayFocusedRootTask(); |
| validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask, |
| fullscreenRequest, r); |
| reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult); |
| if (validateResult == RESULT_APPROVED) { |
| executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask); |
| } |
| return; |
| } |
| // Initiate the transition. |
| final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller, |
| mService.mWindowManager.mSyncEngine); |
| r.mTransitionController.startCollectOrQueue(transition, |
| (deferred) -> { |
| executeFullscreenRequestTransition(fullscreenRequest, callback, r, |
| transition, deferred); |
| }); |
| } |
| |
| private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback, |
| ActivityRecord r, Transition transition, boolean queued) { |
| final @FullscreenRequestHandler.RequestResult int validateResult; |
| final Task topFocusedRootTask; |
| topFocusedRootTask = mService.getTopDisplayFocusedRootTask(); |
| validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask, |
| fullscreenRequest, r); |
| reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult); |
| if (validateResult != RESULT_APPROVED) { |
| transition.abort(); |
| return; |
| } |
| transition.collect(topFocusedRootTask); |
| executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask); |
| r.mTransitionController.requestStartTransition(transition, topFocusedRootTask, |
| null /* remoteTransition */, null /* displayChange */); |
| transition.setReady(topFocusedRootTask, true); |
| } |
| |
| private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback, |
| @FullscreenRequestHandler.RequestResult int result) { |
| if (callback == null) { |
| return; |
| } |
| Bundle res = new Bundle(); |
| res.putInt(REMOTE_CALLBACK_RESULT_KEY, result); |
| try { |
| callback.sendResult(res); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "client throws an exception back to the server, ignore it"); |
| } |
| } |
| |
| private static void executeMultiWindowFullscreenRequest(int fullscreenRequest, Task requester) { |
| final int targetWindowingMode; |
| if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) { |
| requester.mMultiWindowRestoreWindowingMode = |
| requester.getRequestedOverrideWindowingMode(); |
| targetWindowingMode = WINDOWING_MODE_FULLSCREEN; |
| } else { |
| targetWindowingMode = requester.mMultiWindowRestoreWindowingMode; |
| requester.mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE; |
| } |
| requester.setWindowingMode(targetWindowingMode); |
| if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) { |
| requester.setBounds(null); |
| } |
| } |
| |
| @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 { |
| final String callingAttributionTag; |
| 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; |
| } |
| callingAttributionTag = top.launchedFromFeatureId; |
| } |
| return mAssistUtils.showSessionForActiveService(args, SHOW_SOURCE_APPLICATION, |
| callingAttributionTag, 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"); |
| final String callingAttributionTag; |
| 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; |
| callingAttributionTag = activity.launchedFromFeatureId; |
| } |
| LocalServices.getService(VoiceInteractionManagerInternal.class) |
| .startLocalVoiceInteraction(callingActivity, callingAttributionTag, 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); |
| } |
| } |
| |
| public void setAllowCrossUidActivitySwitchFromBelow(IBinder token, boolean allowed) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null) { |
| r.setAllowCrossUidActivitySwitchFromBelow(allowed); |
| } |
| } |
| } 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) { |
| mTaskSupervisor.getActivityMetricsLogger().notifyFullyDrawn(r, |
| restoredFromBundle); |
| } |
| } |
| } finally { |
| Binder.restoreCallingIdentity(origId); |
| } |
| } |
| |
| @Override |
| public void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim, |
| int backgroundColor) { |
| final long origId = Binder.clearCallingIdentity(); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null) { |
| r.overrideCustomTransition(open, enterAnim, exitAnim, backgroundColor); |
| } |
| } |
| Binder.restoreCallingIdentity(origId); |
| } |
| |
| @Override |
| public void clearOverrideActivityTransition(IBinder token, boolean open) { |
| final long origId = Binder.clearCallingIdentity(); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null) { |
| r.clearCustomTransition(open); |
| } |
| } |
| Binder.restoreCallingIdentity(origId); |
| } |
| |
| @Override |
| public void overridePendingTransition(IBinder token, String packageName, |
| int enterAnim, int exitAnim, @ColorInt int backgroundColor) { |
| final long origId = Binder.clearCallingIdentity(); |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null && r.isState(RESUMED, PAUSING)) { |
| r.mDisplayContent.mAppTransition.overridePendingAppTransition( |
| packageName, enterAnim, exitAnim, backgroundColor, null, null, |
| r.mOverrideTaskTransition); |
| r.mTransitionController.setOverrideAnimation( |
| TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName, |
| enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), |
| null /* startCallback */, null /* finishCallback */); |
| } |
| } |
| 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 setRecentsScreenshotEnabled(IBinder token, boolean enabled) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r != null) { |
| r.setRecentsScreenshotEnabled(enabled); |
| } |
| } |
| } 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); |
| } |
| } |
| |
| /** |
| * Removes the outdated home task snapshot. |
| * |
| * @param token The token of the home task, or null if you have the |
| * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS} |
| * permission and want us to find the home task token for you. |
| */ |
| @Override |
| public void invalidateHomeTaskSnapshot(IBinder token) { |
| if (token == null) { |
| ActivityTaskManagerService.enforceTaskPermission("invalidateHomeTaskSnapshot"); |
| } |
| |
| synchronized (mGlobalLock) { |
| final ActivityRecord r; |
| if (token == null) { |
| final Task rootTask = |
| mService.mRootWindowContainer.getDefaultTaskDisplayArea().getRootHomeTask(); |
| r = rootTask != null ? rootTask.topRunningActivity() : null; |
| } else { |
| 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); |
| } |
| } |
| |
| /** |
| * Return {@code true} when the given Activity is a relative Task root. That is, the rest of |
| * the Activities in the Task should be finished when it finishes. Otherwise, return {@code |
| * false}. |
| */ |
| private static boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) { |
| // Not a relative root if the given Activity is not the root Activity of its TaskFragment. |
| final TaskFragment taskFragment = r.getTaskFragment(); |
| if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r, |
| false /* traverseTopToBottom */)) { |
| return false; |
| } |
| |
| // The given Activity is the relative Task root if its TaskFragment is a companion |
| // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together). |
| return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment; |
| } |
| |
| private static boolean isTopActivityInTaskFragment(ActivityRecord activity) { |
| return activity.getTaskFragment().topRunningActivity() == activity; |
| } |
| |
| private void requestCallbackFinish(IRequestFinishCallback callback) { |
| try { |
| callback.requestFinish(); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to invoke request finish callback", e); |
| } |
| } |
| |
| @Override |
| public void onBackPressed(IBinder token, IRequestFinishCallback callback) { |
| final long origId = Binder.clearCallingIdentity(); |
| try { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); |
| if (r == null) return; |
| |
| final Task task = r.getTask(); |
| final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/, |
| true /*setToBottomIfNone*/); |
| if (r == root && mService.mWindowOrganizerController.mTaskOrganizerController |
| .handleInterceptBackPressedOnTaskRoot(r.getRootTask())) { |
| // This task is handled by a task organizer that has requested the back |
| // pressed callback. |
| return; |
| } |
| if (shouldMoveTaskToBack(r, root)) { |
| moveActivityTaskToBack(token, true /* nonRoot */); |
| return; |
| } |
| } |
| |
| // The default option for handling the back button is to finish the Activity. |
| requestCallbackFinish(callback); |
| } finally { |
| Binder.restoreCallingIdentity(origId); |
| } |
| } |
| |
| static boolean shouldMoveTaskToBack(ActivityRecord r, ActivityRecord rootActivity) { |
| if (r != rootActivity && !isRelativeTaskRootActivity(r, rootActivity)) { |
| return false; |
| } |
| final boolean isBaseActivity = rootActivity.mActivityComponent.equals( |
| r.getTask().realActivity); |
| final Intent baseActivityIntent = isBaseActivity ? rootActivity.intent : null; |
| |
| // If the activity was launched directly from the home screen, 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 activity is the last running activity in the task. |
| // 2. The current activity is the base activity for the task. |
| // 3. The activity was launched by the home process, and is one of the main entry |
| // points for the application. |
| return baseActivityIntent != null |
| && isTopActivityInTaskFragment(r) |
| && rootActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME) |
| && ActivityRecord.isMainIntent(baseActivityIntent); |
| } |
| |
| @Override |
| public void enableTaskLocaleOverride(IBinder token) { |
| if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { |
| // Only allow system to align locale. |
| return; |
| } |
| |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.forTokenLocked(token); |
| if (r != null) { |
| r.getTask().mAlignActivityLocaleWithTask = true; |
| } |
| } |
| } |
| |
| /** |
| * Returns {@code true} if the activity was explicitly requested to be launched in its |
| * current TaskFragment. |
| * |
| * @see ActivityRecord#mRequestedLaunchingTaskFragmentToken |
| */ |
| public boolean isRequestedToLaunchInTaskFragment(IBinder activityToken, |
| IBinder taskFragmentToken) { |
| synchronized (mGlobalLock) { |
| final ActivityRecord r = ActivityRecord.isInRootTaskLocked(activityToken); |
| if (r == null) return false; |
| |
| return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken; |
| } |
| } |
| } |