blob: 21c5886f085ba1e78c69f8cd05458be66d15b71a [file] [log] [blame]
/*
* Copyright (C) 2021 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.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
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_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
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.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.TaskFragmentProto.ACTIVITY_TYPE;
import static com.android.server.wm.TaskFragmentProto.DISPLAY_ID;
import static com.android.server.wm.TaskFragmentProto.MIN_HEIGHT;
import static com.android.server.wm.TaskFragmentProto.MIN_WIDTH;
import static com.android.server.wm.TaskFragmentProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowContainerChildProto.TASK_FRAGMENT;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.ResultInfo;
import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.am.HostingRecord;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* A basic container that can be used to contain activities or other {@link TaskFragment}, which
* also able to manage the activity lifecycle and updates the visibilities of the activities in it.
*/
class TaskFragment extends WindowContainer<WindowContainer> {
@IntDef(prefix = {"TASK_FRAGMENT_VISIBILITY"}, value = {
TASK_FRAGMENT_VISIBILITY_VISIBLE,
TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
TASK_FRAGMENT_VISIBILITY_INVISIBLE,
})
@interface TaskFragmentVisibility {}
/**
* TaskFragment is visible. No other TaskFragment(s) on top that fully or partially occlude it.
*/
static final int TASK_FRAGMENT_VISIBILITY_VISIBLE = 0;
/** TaskFragment is partially occluded by other translucent TaskFragment(s) on top of it. */
static final int TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
/** TaskFragment is completely invisible. */
static final int TASK_FRAGMENT_VISIBILITY_INVISIBLE = 2;
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskFragment" : TAG_ATM;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
/** Set to false to disable the preview that is shown while a new activity is being started. */
static final boolean SHOW_APP_STARTING_PREVIEW = true;
/**
* An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
* {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
* indicate that an Activity can be embedded successfully.
*/
static final int EMBEDDING_ALLOWED = 0;
/**
* An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
* {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
* indicate that an Activity can't be embedded because either the Activity does not allow
* untrusted embedding, and the embedding host app is not trusted.
*/
static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1;
/**
* An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
* {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
* indicate that an Activity can't be embedded because this taskFragment's bounds are
* {@link #smallerThanMinDimension(ActivityRecord)}.
*/
static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2;
/**
* An embedding check result of
* {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
* indicate that an Activity can't be embedded because the Activity is started on a new task.
*/
static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
/**
* Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
* {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}.
*/
@IntDef(prefix = {"EMBEDDING_"}, value = {
EMBEDDING_ALLOWED,
EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
EMBEDDING_DISALLOWED_NEW_TASK,
})
@interface EmbeddingCheckResult {}
/**
* Indicate that the minimal width/height should use the default value.
*
* @see #mMinWidth
* @see #mMinHeight
*/
static final int INVALID_MIN_SIZE = -1;
final ActivityTaskManagerService mAtmService;
final ActivityTaskSupervisor mTaskSupervisor;
final RootWindowContainer mRootWindowContainer;
private final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
// TODO(b/233177466): Move mMinWidth and mMinHeight to Task and remove usages in TaskFragment
/**
* Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
* should use the default minimal width.
*/
int mMinWidth;
/**
* Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
* should use the default minimal height.
*/
int mMinHeight;
Dimmer mDimmer = new Dimmer(this);
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
/** The TaskFragment that is adjacent to this one. */
@Nullable
private TaskFragment mAdjacentTaskFragment;
/**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
/**
* The last running activity of the TaskFragment was finished due to clear task while launching
* an activity in the Task.
*/
boolean mClearedTaskForReuse;
/**
* The last running activity of the TaskFragment was reparented to a different Task because it
* is entering PiP.
*/
boolean mClearedTaskFragmentForPip;
/**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
* Only set at leaf task fragments.
*/
@Nullable
private ActivityRecord mPausingActivity = null;
/**
* This is the last activity that we put into the paused state. This is
* used to determine if we need to do an activity transition while sleeping,
* when we normally hold the top activity paused.
*/
ActivityRecord mLastPausedActivity = null;
/**
* Current activity that is resumed, or null if there is none.
* Only set at leaf task fragments.
*/
@Nullable
private ActivityRecord mResumedActivity = null;
/**
* This TaskFragment was created by an organizer which has the following implementations.
* <ul>
* <li>The TaskFragment won't be removed when it is empty. Removal has to be an explicit
* request from the organizer.</li>
* <li>If this fragment is a Task object then unlike other non-root tasks, it's direct
* children are visible to the organizer for ordering purposes.</li>
* <li>A TaskFragment can be created by {@link android.window.TaskFragmentOrganizer}, and
* a Task can be created by {@link android.window.TaskOrganizer}.</li>
* </ul>
*/
@VisibleForTesting
boolean mCreatedByOrganizer;
/** Whether this TaskFragment is embedded in a task. */
private final boolean mIsEmbedded;
/** Organizer that organizing this TaskFragment. */
@Nullable
private ITaskFragmentOrganizer mTaskFragmentOrganizer;
private int mTaskFragmentOrganizerUid = INVALID_UID;
private @Nullable String mTaskFragmentOrganizerProcessName;
/** Client assigned unique token for this TaskFragment if this is created by an organizer. */
@Nullable
private final IBinder mFragmentToken;
/**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
* opportunity to perform animations and finishing the adjacent TaskFragment.
*/
private boolean mDelayLastActivityRemoval;
final Point mLastSurfaceSize = new Point();
private final Rect mTmpInsets = new Rect();
private final Rect mTmpBounds = new Rect();
private final Rect mTmpFullBounds = new Rect();
private final Rect mTmpStableBounds = new Rect();
private final Rect mTmpNonDecorBounds = new Rect();
//TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
// implemented
HashMap<String, SurfaceControl.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
new EnsureVisibleActivitiesConfigHelper();
private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
private boolean mUpdateConfig;
private boolean mPreserveWindow;
private boolean mBehindFullscreen;
void reset(boolean preserveWindow) {
mPreserveWindow = preserveWindow;
mUpdateConfig = false;
mBehindFullscreen = false;
}
void process(ActivityRecord start, boolean preserveWindow) {
if (start == null || !start.mVisibleRequested) {
return;
}
reset(preserveWindow);
forAllActivities(this, start, true /* includeBoundary */,
true /* traverseTopToBottom */);
if (mUpdateConfig) {
// Ensure the resumed state of the focus activity if we updated the configuration of
// any activity.
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
}
@Override
public boolean test(ActivityRecord r) {
mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
mBehindFullscreen |= r.occludesParent();
return mBehindFullscreen;
}
}
/** Creates an embedded task fragment. */
TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
boolean createdByOrganizer) {
this(atmService, fragmentToken, createdByOrganizer, true /* isEmbedded */);
}
TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
boolean createdByOrganizer, boolean isEmbedded) {
super(atmService.mWindowManager);
mAtmService = atmService;
mTaskSupervisor = mAtmService.mTaskSupervisor;
mRootWindowContainer = mAtmService.mRootWindowContainer;
mCreatedByOrganizer = createdByOrganizer;
mIsEmbedded = isEmbedded;
mTaskFragmentOrganizerController =
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
mFragmentToken = fragmentToken;
mRemoteToken = new RemoteToken(this);
}
@NonNull
static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
@NonNull ActivityTaskManagerService service) {
if (token == null) return null;
return service.mWindowOrganizerController.getTaskFragment(token);
}
void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
if (mAdjacentTaskFragment == taskFragment) {
return;
}
resetAdjacentTaskFragment();
if (taskFragment != null) {
mAdjacentTaskFragment = taskFragment;
taskFragment.setAdjacentTaskFragment(this);
}
}
void resetAdjacentTaskFragment() {
// Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
mAdjacentTaskFragment.mAdjacentTaskFragment = null;
mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
}
mAdjacentTaskFragment = null;
mDelayLastActivityRemoval = false;
}
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@NonNull String processName) {
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
mTaskFragmentOrganizerUid = uid;
mTaskFragmentOrganizerProcessName = processName;
}
/** Whether this TaskFragment is organized by the given {@code organizer}. */
boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
return organizer != null && mTaskFragmentOrganizer != null
&& organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
}
TaskFragment getAdjacentTaskFragment() {
return mAdjacentTaskFragment;
}
/** Returns the currently topmost resumed activity. */
@Nullable
ActivityRecord getTopResumedActivity() {
final ActivityRecord taskFragResumedActivity = getResumedActivity();
for (int i = getChildCount() - 1; i >= 0; --i) {
WindowContainer<?> child = getChildAt(i);
ActivityRecord topResumedActivity = null;
if (taskFragResumedActivity != null && child == taskFragResumedActivity) {
topResumedActivity = child.asActivityRecord();
} else if (child.asTaskFragment() != null) {
topResumedActivity = child.asTaskFragment().getTopResumedActivity();
}
if (topResumedActivity != null) {
return topResumedActivity;
}
}
return null;
}
/**
* Returns the currently resumed activity in this TaskFragment's
* {@link #mChildren direct children}
*/
ActivityRecord getResumedActivity() {
return mResumedActivity;
}
void setResumedActivity(ActivityRecord r, String reason) {
warnForNonLeafTaskFragment("setResumedActivity");
if (mResumedActivity == r) {
return;
}
if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
+ mResumedActivity + " to:" + r + " reason:" + reason);
}
if (r != null && mResumedActivity == null) {
// Task is becoming active.
getTask().touchActiveTime();
}
final ActivityRecord prevR = mResumedActivity;
mResumedActivity = r;
mTaskSupervisor.updateTopResumedActivityIfNeeded();
if (r == null && prevR.mDisplayContent != null
&& prevR.mDisplayContent.getFocusedRootTask() == null) {
// Only need to notify DWPC when no activity will resume.
prevR.mDisplayContent.onRunningActivityChanged();
} else if (r != null) {
r.mDisplayContent.onRunningActivityChanged();
}
}
@VisibleForTesting
void setPausingActivity(ActivityRecord pausing) {
mPausingActivity = pausing;
}
/** Returns the currently topmost pausing activity. */
@Nullable
ActivityRecord getTopPausingActivity() {
final ActivityRecord taskFragPausingActivity = getPausingActivity();
for (int i = getChildCount() - 1; i >= 0; --i) {
WindowContainer<?> child = getChildAt(i);
ActivityRecord topPausingActivity = null;
if (taskFragPausingActivity != null && child == taskFragPausingActivity) {
topPausingActivity = child.asActivityRecord();
} else if (child.asTaskFragment() != null) {
topPausingActivity = child.asTaskFragment().getTopPausingActivity();
}
if (topPausingActivity != null) {
return topPausingActivity;
}
}
return null;
}
ActivityRecord getPausingActivity() {
return mPausingActivity;
}
int getDisplayId() {
final DisplayContent dc = getDisplayContent();
return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
}
@Nullable
Task getTask() {
if (asTask() != null) {
return asTask();
}
TaskFragment parent = getParent() != null ? getParent().asTaskFragment() : null;
return parent != null ? parent.getTask() : null;
}
@Override
@Nullable
TaskDisplayArea getDisplayArea() {
return (TaskDisplayArea) super.getDisplayArea();
}
@Override
public boolean isAttached() {
final TaskDisplayArea taskDisplayArea = getDisplayArea();
return taskDisplayArea != null && !taskDisplayArea.isRemoved();
}
/**
* Returns the root {@link TaskFragment}, which is usually also a {@link Task}.
*/
@NonNull
TaskFragment getRootTaskFragment() {
final WindowContainer parent = getParent();
if (parent == null) return this;
final TaskFragment parentTaskFragment = parent.asTaskFragment();
return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
}
@Nullable
Task getRootTask() {
return getRootTaskFragment().asTask();
}
@Override
TaskFragment asTaskFragment() {
return this;
}
@Override
boolean isEmbedded() {
if (mIsEmbedded) {
return true;
}
final WindowContainer<?> parent = getParent();
if (parent != null) {
final TaskFragment taskFragment = parent.asTaskFragment();
return taskFragment != null && taskFragment.isEmbedded();
}
return false;
}
@EmbeddingCheckResult
int isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid);
}
/**
* Checks if the organized task fragment is allowed to have the specified activity, which is
* allowed if an activity allows embedding in untrusted mode, if the trusted mode can be
* enabled, or if the organized task fragment bounds are not
* {@link #smallerThanMinDimension(ActivityRecord)}.
*
* @param uid uid of the TaskFragment organizer.
* @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
*/
@EmbeddingCheckResult
int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
if (!isAllowedToEmbedActivityInUntrustedMode(a)
&& !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
} else if (smallerThanMinDimension(a)) {
return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
}
return EMBEDDING_ALLOWED;
}
boolean smallerThanMinDimension(@NonNull ActivityRecord activity) {
final Rect taskFragBounds = getBounds();
final Task task = getTask();
// Don't need to check if the bounds match parent Task bounds because the fallback mechanism
// is to reparent the Activity to parent if minimum dimensions are not satisfied.
if (task == null || taskFragBounds.equals(task.getBounds())) {
return false;
}
final Point minDimensions = activity.getMinDimensions();
if (minDimensions == null) {
return false;
}
final int minWidth = minDimensions.x;
final int minHeight = minDimensions.y;
return taskFragBounds.width() < minWidth
|| taskFragBounds.height() < minHeight;
}
/**
* Checks if the organized task fragment is allowed to embed activity in untrusted mode.
*/
boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) {
final WindowContainer parent = getParent();
if (parent == null || !parent.getBounds().contains(getBounds())) {
// Without full trust between the host and the embedded activity, we don't allow
// TaskFragment to have bounds outside of the parent bounds.
return false;
}
return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
== FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
}
boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) {
return isAllowedToEmbedActivityInTrustedMode(a, mTaskFragmentOrganizerUid);
}
/**
* Checks if the organized task fragment is allowed to embed activity in fully trusted mode,
* which means that all transactions are allowed. This is supported in the following cases:
* <li>the activity belongs to the same app as the organizer host;</li>
* <li>the activity has declared the organizer host as trusted explicitly via known
* certificate.</li>
* @param uid uid of the TaskFragment organizer.
*/
boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a, int uid) {
if (isFullyTrustedEmbedding(a, uid)) {
return true;
}
Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();
if (knownActivityEmbeddingCerts.isEmpty()) {
// An application must either declare that it allows untrusted embedding, or specify
// a set of app certificates that are allowed to embed it in trusted mode.
return false;
}
AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
.getPackage(uid);
return hostPackage != null && hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
knownActivityEmbeddingCerts);
}
/**
* It is fully trusted for embedding in the system app or embedding in the same app. This is
* different from {@link #isAllowedToBeEmbeddedInTrustedMode()} since there may be a small
* chance for a previous trusted app to start doing something bad.
*/
private static boolean isFullyTrustedEmbedding(@NonNull ActivityRecord a, int uid) {
// The system is trusted to embed other apps securely and for all users.
return UserHandle.getAppId(uid) == SYSTEM_UID
// Activities from the same UID can be embedded freely by the host.
|| a.isUid(uid);
}
/**
* Checks if all activities in the task fragment are embedded as fully trusted.
* @see #isFullyTrustedEmbedding(ActivityRecord, int)
* @param uid uid of the TaskFragment organizer.
*/
boolean isFullyTrustedEmbedding(int uid) {
// Traverse all activities to see if any of them are not fully trusted embedding.
return !forAllActivities(r -> !isFullyTrustedEmbedding(r, uid));
}
/**
* Checks if all activities in the task fragment are allowed to be embedded in trusted mode.
* @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
*/
boolean isAllowedToBeEmbeddedInTrustedMode() {
// Traverse all activities to see if any of them are not in the trusted mode.
return !forAllActivities(r -> !isAllowedToEmbedActivityInTrustedMode(r));
}
/**
* Returns the TaskFragment that is being organized, which could be this or the ascendant
* TaskFragment.
*/
@Nullable
TaskFragment getOrganizedTaskFragment() {
if (mTaskFragmentOrganizer != null) {
return this;
}
TaskFragment parentTaskFragment = getParent() != null ? getParent().asTaskFragment() : null;
return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null;
}
/**
* Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}.
*/
private void warnForNonLeafTaskFragment(String func) {
if (!isLeafTaskFragment()) {
Slog.w(TAG, func + " on non-leaf task fragment " + this);
}
}
boolean hasDirectChildActivities() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asActivityRecord() != null) {
return true;
}
}
return false;
}
void cleanUpActivityReferences(@NonNull ActivityRecord r) {
if (mPausingActivity != null && mPausingActivity == r) {
mPausingActivity = null;
}
if (mResumedActivity != null && mResumedActivity == r) {
setResumedActivity(null, "cleanUpActivityReferences");
}
r.removeTimeouts();
}
/**
* Returns whether this TaskFragment is currently forced to be hidden for any reason.
*/
protected boolean isForceHidden() {
return false;
}
boolean isLeafTaskFragment() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asTaskFragment() != null) {
return false;
}
}
return true;
}
/**
* This should be called when an child activity changes state. This should only
* be called from
* {@link ActivityRecord#setState(ActivityRecord.State, String)} .
* @param record The {@link ActivityRecord} whose state has changed.
* @param state The new state.
* @param reason The reason for the change.
*/
void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
String reason) {
warnForNonLeafTaskFragment("onActivityStateChanged");
if (record == mResumedActivity && state != RESUMED) {
setResumedActivity(null, reason + " - onActivityStateChanged");
}
if (state == RESUMED) {
if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);
}
setResumedActivity(record, reason + " - onActivityStateChanged");
if (record == mRootWindowContainer.getTopResumedActivity()) {
mAtmService.setResumedActivityUncheckLocked(record, reason);
}
mTaskSupervisor.mRecentTasks.add(record.getTask());
}
}
/**
* Resets local parameters because an app's activity died.
* @param app The app of the activity that died.
* @return {@code true} if the process of the pausing activity is died.
*/
boolean handleAppDied(WindowProcessController app) {
warnForNonLeafTaskFragment("handleAppDied");
boolean isPausingDied = false;
if (mPausingActivity != null && mPausingActivity.app == app) {
ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
mPausingActivity);
mPausingActivity = null;
isPausingDied = true;
}
if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
mLastPausedActivity = null;
}
return isPausingDied;
}
void awakeFromSleeping() {
if (mPausingActivity != null) {
Slog.d(TAG, "awakeFromSleeping: previously pausing activity didn't pause");
mPausingActivity.activityPaused(true);
}
}
/**
* Tries to put the activities in the task fragment to sleep.
*
* If the task fragment is not in a state where its activities can be put to sleep, this
* function will start any necessary actions to move the task fragment into such a state.
* It is expected that this function get called again when those actions complete.
*
* @param shuttingDown {@code true} when the called because the device is shutting down.
* @return true if the root task finished going to sleep, false if the root task only started
* the process of going to sleep (checkReadyForSleep will be called when that process finishes).
*/
boolean sleepIfPossible(boolean shuttingDown) {
boolean shouldSleep = true;
if (mResumedActivity != null) {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
"sleep");
shouldSleep = false;
} else if (mPausingActivity != null) {
// Still waiting for something to pause; can't sleep yet.
ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
shouldSleep = false;
}
if (!shuttingDown) {
if (containsStoppingActivity()) {
// Still need to tell some activities to stop; can't sleep yet.
ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
mTaskSupervisor.mStoppingActivities.size());
mTaskSupervisor.scheduleIdle();
shouldSleep = false;
}
}
if (shouldSleep) {
updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
!PRESERVE_WINDOWS, true /* notifyClients */);
}
return shouldSleep;
}
private boolean containsStoppingActivity() {
for (int i = mTaskSupervisor.mStoppingActivities.size() - 1; i >= 0; --i) {
ActivityRecord r = mTaskSupervisor.mStoppingActivities.get(i);
if (r.getTaskFragment() == this) {
return true;
}
}
return false;
}
/**
* Returns true if the TaskFragment is translucent and can have other contents visible behind
* it if needed. A TaskFragment is considered translucent if it don't contain a visible or
* starting (about to be visible) activity that is fullscreen (opaque).
* @param starting The currently starting activity or null if there is none.
*/
@VisibleForTesting
boolean isTranslucent(ActivityRecord starting) {
if (!isAttached() || isForceHidden()) {
return true;
}
final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
PooledLambda.__(ActivityRecord.class), starting);
final ActivityRecord opaque = getActivity(p);
p.recycle();
return opaque == null;
}
private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
if (r.finishing) {
// We don't factor in finishing activities when determining translucency since
// they will be gone soon.
return false;
}
if (!r.visibleIgnoringKeyguard && r != starting) {
// Also ignore invisible activities that are not the currently starting
// activity (about to be visible).
return false;
}
if (r.occludesParent()) {
// Root task isn't translucent if it has at least one fullscreen activity
// that is visible.
return true;
}
return false;
}
ActivityRecord getTopNonFinishingActivity() {
return getTopNonFinishingActivity(true /* includeOverlays */);
}
ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */);
}
/**
* Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
* the current user.
* @param includeOverlays whether the task overlay activity should be included.
* @param includingEmbeddedTask whether the activity in a task that being embedded from this
* one should be included.
* @see #topRunningActivity(boolean, boolean)
*/
ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
boolean includingEmbeddedTask) {
// Split into 4 to avoid object creation due to variable capture.
if (includeOverlays) {
if (includingEmbeddedTask) {
return getActivity((r) -> !r.finishing);
}
return getActivity((r) -> !r.finishing && r.getTask() == this.getTask());
}
if (includingEmbeddedTask) {
return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
}
return getActivity(
(r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask());
}
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* focusableOnly */);
}
ActivityRecord topRunningActivity(boolean focusableOnly) {
return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */);
}
/**
* Returns the top-most running activity, which the activity is non-finishing and ok to show
* to the current user.
*
* @see ActivityRecord#canBeTopRunning()
*/
ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) {
// Split into 4 to avoid object creation due to variable capture.
if (focusableOnly) {
if (includingEmbeddedTask) {
return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
}
return getActivity(
(r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask());
}
if (includingEmbeddedTask) {
return getActivity(ActivityRecord::canBeTopRunning);
}
return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask());
}
int getNonFinishingActivityCount() {
final int[] runningActivityCount = new int[1];
forAllActivities(a -> {
if (!a.finishing) {
runningActivityCount[0]++;
}
});
return runningActivityCount[0];
}
boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
return r != null ? r.isFocusable()
: (isFocusable() && getWindowConfiguration().canReceiveKeys());
}
/**
* Returns the visibility state of this TaskFragment.
*
* @param starting The currently starting activity or null if there is none.
*/
@TaskFragmentVisibility
int getVisibility(ActivityRecord starting) {
if (!isAttached() || isForceHidden()) {
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
if (isTopActivityLaunchedBehind()) {
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
boolean gotTranslucentFullscreen = false;
boolean gotTranslucentAdjacent = false;
boolean shouldBeVisible = true;
// This TaskFragment is only considered visible if all its parent TaskFragments are
// considered visible, so check the visibility of all ancestor TaskFragment first.
final WindowContainer parent = getParent();
if (parent.asTaskFragment() != null) {
final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
// Can't be visible if parent isn't visible
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
} else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
// Parent is behind a translucent container so the highest visibility this container
// can get is that.
gotTranslucentFullscreen = true;
}
}
final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer other = parent.getChildAt(i);
if (other == null) continue;
final boolean hasRunningActivities = hasRunningActivity(other);
if (other == this) {
if (!adjacentTaskFragments.isEmpty() && !gotTranslucentAdjacent) {
// The z-order of this TaskFragment is in middle of two adjacent TaskFragments
// and it cannot be visible if the TaskFragment on top is not translucent and
// is occluding this one.
mTmpRect.set(getBounds());
for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) {
final TaskFragment taskFragment = adjacentTaskFragments.get(j);
final TaskFragment adjacentTaskFragment =
taskFragment.mAdjacentTaskFragment;
if (adjacentTaskFragment == this) {
continue;
}
if (mTmpRect.intersect(taskFragment.getBounds())
|| mTmpRect.intersect(adjacentTaskFragment.getBounds())) {
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
}
}
// Should be visible if there is no other fragment occluding it, unless it doesn't
// have any running activities, not starting one and not home stack.
shouldBeVisible = hasRunningActivities
|| (starting != null && starting.isDescendantOf(this))
|| isActivityTypeHome();
break;
}
if (!hasRunningActivities) {
continue;
}
final int otherWindowingMode = other.getWindowingMode();
if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
if (isTranslucent(other, starting)) {
// Can be visible behind a translucent fullscreen TaskFragment.
gotTranslucentFullscreen = true;
continue;
}
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
} else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
&& other.matchParentBounds()) {
if (isTranslucent(other, starting)) {
// Can be visible behind a translucent TaskFragment.
gotTranslucentFullscreen = true;
continue;
}
// Multi-window TaskFragment that matches parent bounds would occlude other children
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
final TaskFragment otherTaskFrag = other.asTaskFragment();
if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) {
if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
if (otherTaskFrag.isTranslucent(starting)
|| otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) {
// Can be visible behind a translucent adjacent TaskFragments.
gotTranslucentFullscreen = true;
gotTranslucentAdjacent = true;
continue;
}
// Can not be visible behind adjacent TaskFragments.
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
} else {
adjacentTaskFragments.add(otherTaskFrag);
}
}
}
if (!shouldBeVisible) {
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
// Lastly - check if there is a translucent fullscreen TaskFragment on top.
return gotTranslucentFullscreen
? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
: TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
private static boolean hasRunningActivity(WindowContainer wc) {
if (wc.asTaskFragment() != null) {
return wc.asTaskFragment().topRunningActivity() != null;
}
return wc.asActivityRecord() != null && !wc.asActivityRecord().finishing;
}
private static boolean isTranslucent(WindowContainer wc, ActivityRecord starting) {
if (wc.asTaskFragment() != null) {
return wc.asTaskFragment().isTranslucent(starting);
} else if (wc.asActivityRecord() != null) {
return !wc.asActivityRecord().occludesParent();
}
return false;
}
private boolean isTopActivityLaunchedBehind() {
final ActivityRecord top = topRunningActivity();
return top != null && top.mLaunchTaskBehind;
}
final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
mEnsureActivitiesVisibleHelper.process(
starting, configChanges, preserveWindows, notifyClients);
} finally {
mTaskSupervisor.endActivityVisibilityUpdate();
}
}
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
boolean deferPause) {
ActivityRecord next = topRunningActivity(true /* focusableOnly */);
if (next == null || !next.canResumeByCompat()) {
return false;
}
next.delayedResume = false;
final TaskDisplayArea taskDisplayArea = getDisplayArea();
// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.allResumedActivitiesComplete()) {
// Ensure the visibility gets updated before execute app transition.
taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
false /* preserveWindows */, true /* notifyClients */);
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
// In a multi-resumed environment, like in a freeform device, the top
// activity can be resumed, but it might not be the focused app.
// Set focused app when top activity is resumed
if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null
&& taskDisplayArea.mDisplayContent.mFocusedApp != next) {
taskDisplayArea.mDisplayContent.setFocusedApp(next);
}
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
+ "resumed %s", next);
return false;
}
// If we are currently pausing an activity, then don't do anything until that is done.
final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
if (!allPausedComplete) {
ProtoLog.v(WM_DEBUG_STATES,
"resumeTopActivity: Skip resume: some activity pausing.");
return false;
}
// If we are sleeping, and there is no resumed activity, and the top activity is paused,
// well that is the state we want.
if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Going to sleep and"
+ " all paused");
return false;
}
// Make sure that the user who owns this activity is started. If not,
// we will just leave it as is because someone should be bringing
// another user's activities to the top of the stack.
if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
Slog.w(TAG, "Skipping resume of top activity " + next
+ ": user " + next.mUserId + " is stopped");
return false;
}
// The activity may be waiting for stop, but that is no longer
// appropriate for it.
mTaskSupervisor.mStoppingActivities.remove(next);
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
ActivityRecord lastResumed = null;
final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTaskFragment().asTask()) {
// So, why aren't we using prev here??? See the param comment on the method. prev
// doesn't represent the last resumed activity. However, the last focus stack does if
// it isn't null.
lastResumed = lastFocusedRootTask.getTopResumedActivity();
}
boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
if (mResumedActivity != null) {
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
next, "resumeTopActivity");
}
if (pausing) {
ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"
+ " start pausing");
// At this point we want to put the upcoming activity's process
// at the top of the LRU list, since we know we will be needing it
// very soon and it would be a waste to let it get killed if it
// happens to be sitting towards the end.
if (next.attachedToProcess()) {
next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
true /* activityChange */, false /* updateOomAdj */,
false /* addPendingTopUid */);
} else if (!next.isProcessRunning()) {
// Since the start-process is asynchronous, if we already know the process of next
// activity isn't running, we can start the process earlier to save the time to wait
// for the current activity to be paused.
final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
isTop ? HostingRecord.HOSTING_TYPE_NEXT_TOP_ACTIVITY
: HostingRecord.HOSTING_TYPE_NEXT_ACTIVITY);
}
if (lastResumed != null) {
lastResumed.setWillCloseOrEnterPip(true);
}
return true;
} else if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.allResumedActivitiesComplete()) {
// It is possible for the activity to be resumed when we paused back stacks above if the
// next activity doesn't have to wait for pause to complete.
// So, nothing else to-do except:
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity resumed "
+ "(dontWaitForPause) %s", next);
return true;
}
// If the most recent activity was noHistory but was only stopped rather
// than stopped+finished because the device went to sleep, we need to make
// sure to finish it as we're making a new activity topmost.
if (shouldSleepActivities()) {
mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
}
if (prev != null && prev != next && next.nowVisible) {
// The next activity is already visible, so hide the previous
// activity's windows right now so we can show the new one ASAP.
// We only do this if the previous is finishing, which should mean
// it is on top of the one being resumed so hiding it quickly
// is good. Otherwise, we want to do the normal route of allowing
// the resumed activity to be shown so we can decide if the
// previous should actually be hidden depending on whether the
// new one is found to be full-screen or not.
if (prev.finishing) {
prev.setVisibility(false);
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev
+ ", nowVisible=" + next.nowVisible);
}
} else {
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev
+ ", nowVisible=" + next.nowVisible);
}
}
}
// Launching this app's activity, make sure the app is no longer
// considered stopped.
try {
mTaskSupervisor.getActivityMetricsLogger()
.notifyBeforePackageUnstopped(next.packageName);
mAtmService.getPackageManager().setPackageStoppedState(
next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
} catch (RemoteException e1) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ next.packageName + ": " + e);
}
// We are starting up the next activity, so tell the window manager
// that the previous one will be hidden soon. This way it can know
// to ignore it when computing the desired screen orientation.
boolean anim = true;
final DisplayContent dc = taskDisplayArea.mDisplayContent;
if (prev != null) {
if (prev.finishing) {
if (DEBUG_TRANSITION) {
Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
}
if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
anim = false;
dc.prepareAppTransition(TRANSIT_NONE);
} else {
dc.prepareAppTransition(TRANSIT_CLOSE);
}
prev.setVisibility(false);
} else {
if (DEBUG_TRANSITION) {
Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
}
if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
anim = false;
dc.prepareAppTransition(TRANSIT_NONE);
} else {
dc.prepareAppTransition(TRANSIT_OPEN,
next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
}
}
} else {
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
anim = false;
dc.prepareAppTransition(TRANSIT_NONE);
} else {
dc.prepareAppTransition(TRANSIT_OPEN);
}
}
if (anim) {
next.applyOptionsAnimation();
} else {
next.abortAndClearOptionsAnimation();
}
mTaskSupervisor.mNoAnimActivities.clear();
if (next.attachedToProcess()) {
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
+ " visibleRequested=" + next.mVisibleRequested);
}
// If the previous activity is translucent, force a visibility update of
// the next activity, so that it's added to WM's opening app list, and
// transition animation can be set up properly.
// For example, pressing Home button with a translucent activity in focus.
// Launcher is already visible in this case. If we don't add it to opening
// apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
// TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
final boolean lastActivityTranslucent = inMultiWindowMode()
|| mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
// This activity is now becoming visible.
if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
next.app.addToPendingTop();
next.setVisibility(true);
}
// schedule launch ticks to collect information about slow apps.
next.startLaunchTickingLocked();
ActivityRecord lastResumedActivity =
lastFocusedRootTask == null ? null
: lastFocusedRootTask.getTopResumedActivity();
final ActivityRecord.State lastState = next.getState();
mAtmService.updateCpuStats();
ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
next.setState(RESUMED, "resumeTopActivity");
// Have the window manager re-evaluate the orientation of
// the screen based on the new activity order.
boolean notUpdated = true;
// Activity should also be visible if set mLaunchTaskBehind to true (see
// ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
if (shouldBeVisible(next)) {
// We have special rotation behavior when here is some active activity that
// requests specific orientation or Keyguard is locked. Make sure all activity
// visibilities are set correctly as well as the transition is updated if needed
// to get the correct rotation behavior. Otherwise the following call to update
// the orientation may cause incorrect configurations delivered to client as a
// result of invisible window resize.
// TODO: Remove this once visibilities are set correctly immediately when
// starting an activity.
notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
true /* markFrozenIfConfigChanged */, false /* deferResume */);
}
if (notUpdated) {
// The configuration update wasn't able to keep the existing
// instance of the activity, and instead started a new one.
// We should be all done, but let's just make sure our activity
// is still at the top and schedule another run if something
// weird happened.
ActivityRecord nextNext = topRunningActivity();
ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
+ "%s, new next: %s", next, nextNext);
if (nextNext != next) {
// Do over!
mTaskSupervisor.scheduleResumeTopActivities();
}
if (!next.mVisibleRequested || next.stopped) {
next.setVisibility(true);
}
next.completeResumeLocked();
return true;
}
try {
final ClientTransaction transaction =
ClientTransaction.obtain(next.app.getThread(), next.token);
// Deliver all pending results.
ArrayList<ResultInfo> a = next.results;
if (a != null) {
final int size = a.size();
if (!next.finishing && size > 0) {
if (DEBUG_RESULTS) {
Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
}
transaction.addCallback(ActivityResultItem.obtain(a));
}
}
if (next.newIntents != null) {
transaction.addCallback(
NewIntentItem.obtain(next.newIntents, true /* resume */));
}
// Well the app will no longer be stopped.
// Clear app token stopped state in window manager if needed.
next.notifyAppResumed(next.stopped);
EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
next.getTask().mTaskId, next.shortComponentName);
mAtmService.getAppWarningsLocked().onResumeActivity(next);
next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
next.abortAndClearOptionsAnimation();
transaction.setLifecycleStateRequest(
ResumeActivityItem.obtain(next.app.getReportedProcState(),
dc.isNextTransitionForward()));
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
} catch (Exception e) {
// Whoops, need to restart this activity!
ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+ "%s", lastState, next);
next.setState(lastState, "resumeTopActivityInnerLocked");
// lastResumedActivity being non-null implies there is a lastStack present.
if (lastResumedActivity != null) {
lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
}
Slog.i(TAG, "Restarting because process died: " + next);
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
} else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
&& lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
next.showStartingWindow(false /* taskSwitch */);
}
mTaskSupervisor.startSpecificActivity(next, true, false);
return true;
}
// From this point on, if something goes wrong there is no way
// to recover the activity.
try {
next.completeResumeLocked();
} catch (Exception e) {
// If any exception gets thrown, toss away this
// activity and try the next one.
Slog.w(TAG, "Exception thrown during resume of " + next, e);
next.finishIfPossible("resume-exception", true /* oomAdj */);
return true;
}
} else {
// Whoops, need to restart this activity!
if (!next.hasBeenLaunched) {
next.hasBeenLaunched = true;
} else {
if (SHOW_APP_STARTING_PREVIEW) {
next.showStartingWindow(false /* taskSwich */);
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
mTaskSupervisor.startSpecificActivity(next, true, true);
}
return true;
}
boolean shouldSleepOrShutDownActivities() {
return shouldSleepActivities() || mAtmService.mShuttingDown;
}
/**
* Returns true if the TaskFragment should be visible.
*
* @param starting The currently starting activity or null if there is none.
*/
boolean shouldBeVisible(ActivityRecord starting) {
return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
/**
* Returns {@code true} is the activity in this TaskFragment can be resumed.
*
* @param starting The currently starting activity or {@code null} if there is none.
*/
boolean canBeResumed(@Nullable ActivityRecord starting) {
// No need to resume activity in TaskFragment that is not visible.
return isTopActivityFocusable()
&& getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
boolean isFocusableAndVisible() {
return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
}
final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) {
return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
}
/**
* Start pausing the currently resumed activity. It is an error to call this if there
* is already an activity being paused or there is no resumed activity.
*
* @param userLeaving True if this should result in an onUserLeaving to the current activity.
* @param uiSleeping True if this is happening with the user interface going to sleep (the
* screen turning off).
* @param resuming The activity we are currently trying to resume or null if this is not being
* called as part of resuming the top activity, so we shouldn't try to instigate
* a resume here if not null.
* @param reason The reason of pausing the activity.
* @return Returns true if an activity now is in the PAUSING state, and we are waiting for
* it to tell us when it is done.
*/
boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
String reason) {
if (!hasDirectChildActivities()) {
return false;
}
ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
mResumedActivity);
if (mPausingActivity != null) {
Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+ " state=" + mPausingActivity.getState());
if (!shouldSleepActivities()) {
// Avoid recursion among check for sleep and complete pause during sleeping.
// Because activity will be paused immediately after resume, just let pause
// be completed by the order of activity paused from clients.
completePause(false, resuming);
}
}
ActivityRecord prev = mResumedActivity;
if (prev == null) {
if (resuming == null) {
Slog.wtf(TAG, "Trying to pause when nothing is resumed");
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
return false;
}
if (prev == resuming) {
Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
return false;
}
ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
mPausingActivity = prev;
mLastPausedActivity = prev;
if (!prev.finishing && prev.isNoHistory()
&& !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
mTaskSupervisor.mNoHistoryActivities.add(prev);
}
prev.setState(PAUSING, "startPausingLocked");
prev.getTask().touchActiveTime();
mAtmService.updateCpuStats();
boolean pauseImmediately = false;
boolean shouldAutoPip = false;
if (resuming != null) {
// Resuming the new resume activity only if the previous activity can't go into Pip
// since we want to give Pip activities a chance to enter Pip before resuming the
// next activity.
final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
"shouldAutoPipWhilePausing", userLeaving);
if (userLeaving && lastResumedCanPip
&& prev.pictureInPictureArgs.isAutoEnterEnabled()) {
shouldAutoPip = true;
} else if (!lastResumedCanPip) {
// If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
// activity to be paused.
pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
} else {
// The previous activity may still enter PIP even though it did not allow auto-PIP.
}
}
if (prev.attachedToProcess()) {
if (shouldAutoPip) {
boolean didAutoPip = mAtmService.enterPictureInPictureMode(
prev, prev.pictureInPictureArgs, false /* fromClient */);
ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
+ "directly: %s, didAutoPip: %b", prev, didAutoPip);
} else {
schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
}
} else {
mPausingActivity = null;
mLastPausedActivity = null;
mTaskSupervisor.mNoHistoryActivities.remove(prev);
}
// If we are not going to sleep, we want to ensure the device is
// awake until the next activity is started.
if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
mTaskSupervisor.acquireLaunchWakelock();
}
// If already entered PIP mode, no need to keep pausing.
if (mPausingActivity != null) {
// Have the window manager pause its key dispatching until the new
// activity has started. If we're pausing the activity just because
// the screen is being turned off and the UI is sleeping, don't interrupt
// key dispatch; the same activity will pick it up again on wakeup.
if (!uiSleeping) {
prev.pauseKeyDispatchingLocked();
} else {
ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
}
if (pauseImmediately) {
// If the caller said they don't want to wait for the pause, then complete
// the pause now.
completePause(false, resuming);
return false;
} else {
prev.schedulePauseTimeout();
// All activities will be stopped when sleeping, don't need to wait for pause.
if (!uiSleeping) {
// Unset readiness since we now need to wait until this pause is complete.
mTransitionController.setReady(this, false /* ready */);
}
return true;
}
} else {
// This activity either failed to schedule the pause or it entered PIP mode,
// so just treat it as being paused now.
ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
if (resuming == null) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
return false;
}
}
void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
boolean pauseImmediately, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
try {
EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
prev.shortComponentName, "userLeaving=" + userLeaving, reason);
mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,
prev.configChangeFlags, pauseImmediately));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
mPausingActivity = null;
mLastPausedActivity = null;
mTaskSupervisor.mNoHistoryActivities.remove(prev);
}
}
@VisibleForTesting
void completePause(boolean resumeNext, ActivityRecord resuming) {
// Complete the pausing process of a pausing activity, so it doesn't make sense to
// operate on non-leaf tasks.
// warnForNonLeafTask("completePauseLocked");
ActivityRecord prev = mPausingActivity;
ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
if (prev != null) {
prev.setWillCloseOrEnterPip(false);
final boolean wasStopping = prev.isState(STOPPING);
prev.setState(PAUSED, "completePausedLocked");
if (prev.finishing) {
// We will update the activity visibility later, no need to do in
// completeFinishing(). Updating visibility here might also making the next
// activities to be resumed, and could result in wrong app transition due to
// lack of previous activity information.
ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
prev = prev.completeFinishing(false /* updateVisibility */,
"completePausedLocked");
} else if (prev.hasProcess()) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,
prev.mVisibleRequested);
if (prev.deferRelaunchUntilPaused) {
// Complete the deferred relaunch that was waiting for pause to complete.
ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
} else if (wasStopping) {
// We are also stopping, the stop request must have gone soon after the pause.
// We can't clobber it, because the stop confirmation will not be handled.
// We don't need to schedule another stop, we only need to let it happen.
prev.setState(STOPPING, "completePausedLocked");
} else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
// Clear out any deferred client hide we might currently have.
prev.setDeferHidingClient(false);
// If we were visible then resumeTopActivities will release resources before
// stopping.
prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
"completePauseLocked");
}
} else {
ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
prev = null;
}
// It is possible the activity was freezing the screen before it was paused.
// In that case go ahead and remove the freeze this activity has on the screen
// since it is no longer visible.
if (prev != null) {
prev.stopFreezingScreenLocked(true /*force*/);
}
mPausingActivity = null;
}
if (resumeNext) {
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,
null /* targetOptions */);
} else {
// checkReadyForSleep();
final ActivityRecord top =
topRootTask != null ? topRootTask.topRunningActivity() : null;
if (top == null || (prev != null && top != prev)) {
// If there are no more activities available to run, do resume anyway to start
// something. Also if the top activity on the root task is not the just paused
// activity, we need to go ahead and resume it to ensure we complete an
// in-flight app switch.
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
}
}
if (prev != null) {
prev.resumeKeyDispatchingLocked();
}
mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
// Notify when the task stack has changed, but only if visibilities changed (not just
// focus). Also if there is an active root pinned task - we always want to notify it about
// task stack changes, because its positioning may depend on it.
if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
|| (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
}
}
@Override
void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
super.forAllTaskFragments(callback, traverseTopToBottom);
callback.accept(this);
}
@Override
void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
boolean isLeafTaskFrag = true;
if (traverseTopToBottom) {
for (int i = count - 1; i >= 0; --i) {
final TaskFragment child = mChildren.get(i).asTaskFragment();
if (child != null) {
isLeafTaskFrag = false;
child.forAllLeafTaskFragments(callback, traverseTopToBottom);
}
}
} else {
for (int i = 0; i < count; i++) {
final TaskFragment child = mChildren.get(i).asTaskFragment();
if (child != null) {
isLeafTaskFrag = false;
child.forAllLeafTaskFragments(callback, traverseTopToBottom);
}
}
}
if (isLeafTaskFrag) callback.accept(this);
}
@Override
boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
boolean isLeafTaskFrag = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final TaskFragment child = mChildren.get(i).asTaskFragment();
if (child != null) {
isLeafTaskFrag = false;
if (child.forAllLeafTaskFragments(callback)) {
return true;
}
}
}
if (isLeafTaskFrag) {
return callback.test(this);
}
return false;
}
void addChild(ActivityRecord r) {
addChild(r, POSITION_TOP);
}
@Override
void addChild(WindowContainer child, int index) {
ActivityRecord r = topRunningActivity();
mClearedTaskForReuse = false;
mClearedTaskFragmentForPip = false;
final ActivityRecord addingActivity = child.asActivityRecord();
final boolean isAddingActivity = addingActivity != null;
final Task task = isAddingActivity ? getTask() : null;
// If this task had any activity before we added this one.
boolean taskHadActivity = task != null && task.getTopMostActivity() != null;
// getActivityType() looks at the top child, so we need to read the type before adding
// a new child in case the new child is on top and UNDEFINED.
final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
super.addChild(child, index);
if (isAddingActivity && task != null) {
// TODO(b/207481538): temporary per-activity screenshoting
if (r != null && BackNavigationController.isScreenshotEnabled()) {
ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
r.mActivityComponent.flattenToString());
Rect outBounds = r.getBounds();
SurfaceControl.ScreenshotHardwareBuffer backBuffer = SurfaceControl.captureLayers(
r.mSurfaceControl,
new Rect(0, 0, outBounds.width(), outBounds.height()),
1f);
mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
}
child.asActivityRecord().inHistory = true;
task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
}
}
@Override
void onChildPositionChanged(WindowContainer child) {
super.onChildPositionChanged(child);
sendTaskFragmentInfoChanged();
}
void executeAppTransition(ActivityOptions options) {
// No app transition applied to the task fragment.
}
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
// There may be a trampoline activity without window on top of the existing task
// which is moving to front. Exclude the finishing activity so the window of next
// activity can be chosen to create the animation target.
? getTopNonFinishingActivity()
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
@Override
boolean canCreateRemoteAnimationTarget() {
return true;
}
boolean shouldSleepActivities() {
return false;
}
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
super.resolveOverrideConfiguration(newParentConfig);
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
// Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
// pinned windowing mode.
if (!supportsMultiWindow()) {
final int candidateWindowingMode =
windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
&& candidateWindowingMode != WINDOWING_MODE_PINNED) {
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
}
}
final Task thisTask = asTask();
// Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute
// configuration here.
if (thisTask != null && !thisTask.isEmbedded()) {
thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
mTmpBounds /* previousBounds */);
}
computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
boolean supportsMultiWindow() {
return supportsMultiWindowInDisplayArea(getDisplayArea());
}
/**
* @return whether this task supports multi-window if it is in the given
* {@link TaskDisplayArea}.
*/
boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
if (!mAtmService.mSupportsMultiWindow) {
return false;
}
if (tda == null) {
return false;
}
final Task task = getTask();
if (task == null) {
return false;
}
if (!task.isResizeable() && !tda.supportsNonResizableMultiWindow()) {
// Not support non-resizable in multi window.
return false;
}
final ActivityRecord rootActivity = task.getRootActivity();
return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight,
rootActivity != null ? rootActivity.info : null);
}
private int getTaskId() {
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
/**
* Ensures all visible activities at or below the input activity have the right configuration.
*/
void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
null /* compatInsets */, false /* areBoundsLetterboxed */);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, boolean areBoundsLetterboxed) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
null /* compatInsets */, areBoundsLetterboxed);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
boolean areBoundsLetterboxed) {
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided display info.
inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
null /* compatInsets */, areBoundsLetterboxed);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig,
@Nullable ActivityRecord.CompatDisplayInsets compatInsets,
boolean areBoundsLetterboxed) {
if (compatInsets != null) {
// Make sure the app bounds can be computed by the compat insets.
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
compatInsets, areBoundsLetterboxed);
}
/**
* Forces the app bounds related configuration can be computed by
* {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
* ActivityRecord.CompatDisplayInsets)}.
*/
private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
if (appBounds != null) {
appBounds.setEmpty();
}
inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
}
/**
* Calculates configuration values used by the client to get resources. This should be run
* using app-facing bounds (bounds unmodified by animations or transient interactions).
*
* This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
* configuring an "inherit-bounds" window which means that all configuration settings would
* just be inherited from the parent configuration.
**/
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
@Nullable ActivityRecord.CompatDisplayInsets compatInsets,
boolean areBoundsLetterboxed) {
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = parentConfig.windowConfiguration.getWindowingMode();
}
float density = inOutConfig.densityDpi;
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
density = parentConfig.densityDpi;
}
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
// The bounds may have been overridden at this level. If the parent cannot cover these
// bounds, the configuration is still computed according to the override bounds.
final boolean insideParentBounds;
final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
if (resolvedBounds == null || resolvedBounds.isEmpty()) {
mTmpFullBounds.set(parentBounds);
insideParentBounds = true;
} else {
mTmpFullBounds.set(resolvedBounds);
insideParentBounds = parentBounds.contains(resolvedBounds);
}
// Non-null compatibility insets means the activity prefers to keep its original size, so
// out bounds doesn't need to be restricted by the parent or current display
final boolean customContainerPolicy = compatInsets != null;
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
// App-bounds hasn't been overridden, so calculate a value for it.
inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
final Rect containingAppBounds;
if (insideParentBounds) {
containingAppBounds = parentConfig.windowConfiguration.getAppBounds();
} else {
// Restrict appBounds to display non-decor rather than parent because the
// override bounds are beyond the parent. Otherwise, it won't match the
// overridden bounds.
final TaskDisplayArea displayArea = getDisplayArea();
containingAppBounds = displayArea != null
? displayArea.getWindowConfiguration().getAppBounds() : null;
}
if (containingAppBounds != null && !containingAppBounds.isEmpty()) {
outAppBounds.intersect(containingAppBounds);
}
}
}
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
|| inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
mTmpNonDecorBounds.set(mTmpFullBounds);
mTmpStableBounds.set(mTmpFullBounds);
} else if (!customContainerPolicy
&& (overrideDisplayInfo != null || getDisplayContent() != null)) {
final DisplayInfo di = overrideDisplayInfo != null
? overrideDisplayInfo
: getDisplayContent().getDisplayInfo();
// For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
int rotation = inOutConfig.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED) {
rotation = parentConfig.windowConfiguration.getRotation();
}
if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
mTmpNonDecorBounds.set(mTmpFullBounds);
mTmpStableBounds.set(mTmpFullBounds);
compatInsets.getBoundsByRotation(mTmpBounds, rotation);
intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
compatInsets.mNonDecorInsets[rotation]);
intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
compatInsets.mStableInsets[rotation]);
outAppBounds.set(mTmpNonDecorBounds);
} else {
// Set to app bounds because it excludes decor insets.
mTmpNonDecorBounds.set(outAppBounds);
mTmpStableBounds.set(outAppBounds);
}
}
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density + 0.5f);
inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
: overrideScreenWidthDp;
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
final int overrideScreenHeightDp =
(int) (mTmpStableBounds.height() / density + 0.5f);
inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
: overrideScreenHeightDp;
}
// TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp.
if (inOutConfig.smallestScreenWidthDp
== Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
// When entering to or exiting from Pip, the PipTaskOrganizer will set the
// windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
// temporarily set the bounds of the task to fullscreen size for transitioning.
// It will get the wrong value if the calculation is based on this temporary
// fullscreen bounds.
// We should just inherit the value from parent for this temporary state.
final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
&& !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
// For floating tasks, calculate the smallest width from the bounds of the
// task, because they should not be affected by insets.
inOutConfig.smallestScreenWidthDp = (int) (0.5f
+ Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
} else if (isEmbedded() || areBoundsLetterboxed || customContainerPolicy) {
// For embedded TFs and activities that are letteboxed or eligible for size
// compat mode, the smallest width should be updated. Otherwise, inherit from
// the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
}
// otherwise, it will just inherit
}
}
if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
}
if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
// For calculating screen layout, we need to use the non-decor inset screen area for the
// calculation for compatibility reasons, i.e. screen area without system bars that
// could never go away in Honeycomb.
int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density + 0.5f);
int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density + 0.5f);
// Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
// undefined so it can't be used.
if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
compatScreenWidthDp = inOutConfig.screenWidthDp;
}
if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
compatScreenHeightDp = inOutConfig.screenHeightDp;
}
// Reducing the screen layout starting from its parent config.
inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
compatScreenWidthDp, compatScreenHeightDp);
}
}
/**
* Gets bounds with non-decor and stable insets applied respectively.
*
* If bounds overhangs the display, those edges will not get insets. See
* {@link #intersectWithInsetsIfFits}
*
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
DisplayInfo displayInfo) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
final Task rootTask = getRootTaskFragment().asTask();
if (rootTask == null || rootTask.mDisplayContent == null) {
return;
}
mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
policy.getNonDecorInsetsLw(displayInfo.rotation,
displayInfo.displayCutout, mTmpInsets);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
}
/**
* Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
* intersectBounds on a side, then the respective side will not be intersected.
*
* The assumption is that if inOutBounds is initially larger than intersectBounds, then the
* inset on that side is no-longer applicable. This scenario happens when a task's minimal
* bounds are larger than the provided parent/display bounds.
*
* @param inOutBounds the bounds to intersect.
* @param intersectBounds the bounds to intersect with.
* @param intersectInsets insets to apply to intersectBounds before intersecting.
*/
static void intersectWithInsetsIfFits(
Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
if (inOutBounds.right <= intersectBounds.right) {
inOutBounds.right =
Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
}
if (inOutBounds.bottom <= intersectBounds.bottom) {
inOutBounds.bottom =
Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
}
if (inOutBounds.left >= intersectBounds.left) {
inOutBounds.left =
Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
}
if (inOutBounds.top >= intersectBounds.top) {
inOutBounds.top =
Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
}
}
/** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
int screenHeightDp) {
sourceScreenLayout = sourceScreenLayout
& (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
final int longSize = Math.max(screenWidthDp, screenHeightDp);
final int shortSize = Math.min(screenWidthDp, screenHeightDp);
return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
}
@Override
public int getActivityType() {
final int applicationType = super.getActivityType();
if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
return applicationType;
}
final ActivityRecord activity = getTopMostActivity();
return activity != null ? activity.getActivityType() : getTopChild().getActivityType();
}
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
// Task will animate differently.
if (mTaskFragmentOrganizer != null) {
mTmpPrevBounds.set(getBounds());
}
super.onConfigurationChanged(newParentConfig);
if (shouldStartChangeTransition(mTmpPrevBounds)) {
initializeChangeTransition(mTmpPrevBounds);
} else if (mTaskFragmentOrganizer != null) {
// Update the surface here instead of in the organizer so that we can make sure
// it can be synced with the surface freezer.
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
}
sendTaskFragmentInfoChanged();
}
/** Updates the surface size so that the sub windows cannot be shown out of bounds. */
private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
boolean forceUpdate) {
if (mTaskFragmentOrganizer == null) {
// We only want to update for organized TaskFragment. Task will handle itself.
return;
}
if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
return;
}
final Rect bounds = getBounds();
final int width = bounds.width();
final int height = bounds.height();
if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
return;
}
t.setWindowCrop(mSurfaceControl, width, height);
mLastSurfaceSize.set(width, height);
}
@Override
public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
super.onAnimationLeashCreated(t, leash);
// Reset surface bounds for animation. It will be taken care by the animation leash, and
// reset again onAnimationLeashLost.
if (mTaskFragmentOrganizer != null
&& (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
t.setWindowCrop(mSurfaceControl, 0, 0);
mLastSurfaceSize.set(0, 0);
}
}
@Override
public void onAnimationLeashLost(SurfaceControl.Transaction t) {
super.onAnimationLeashLost(t);
// Update the surface bounds after animation.
if (mTaskFragmentOrganizer != null) {
updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */);
}
}
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
private boolean shouldStartChangeTransition(Rect startBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
return !startBounds.equals(getBounds());
}
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
if (mTaskFragmentOrganizer != null) {
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
// If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
// emit the callbacks now.
sendTaskFragmentAppeared();
}
}
void sendTaskFragmentInfoChanged() {
if (mTaskFragmentOrganizer != null) {
mTaskFragmentOrganizerController
.onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this);
}
}
private void sendTaskFragmentAppeared() {
if (mTaskFragmentOrganizer != null) {
mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
}
}
private void sendTaskFragmentVanished() {
if (mTaskFragmentOrganizer != null) {
mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this);
}
}
/**
* Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be
* called from {@link Task}.
*/
TaskFragmentInfo getTaskFragmentInfo() {
List<IBinder> childActivities = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
final WindowContainer<?> wc = getChildAt(i);
final ActivityRecord ar = wc.asActivityRecord();
if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
&& ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
&& ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
// Only includes Activities that belong to the organizer process for security.
childActivities.add(ar.token);
}
}
final Point positionInParent = new Point();
getRelativePosition(positionInParent);
return new TaskFragmentInfo(
mFragmentToken,
mRemoteToken.toWindowContainerToken(),
getConfiguration(),
getNonFinishingActivityCount(),
isVisible(),
childActivities,
positionInParent,
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
calculateMinDimension());
}
/**
* Calculates the minimum dimensions that this TaskFragment can be resized.
* @see TaskFragmentInfo#getMinimumWidth()
* @see TaskFragmentInfo#getMinimumHeight()
*/
Point calculateMinDimension() {
final int[] maxMinWidth = new int[1];
final int[] maxMinHeight = new int[1];
forAllActivities(a -> {
if (a.finishing) {
return;
}
final Point minDimensions = a.getMinDimensions();
if (minDimensions == null) {
return;
}
maxMinWidth[0] = Math.max(maxMinWidth[0], minDimensions.x);
maxMinHeight[0] = Math.max(maxMinHeight[0], minDimensions.y);
});
return new Point(maxMinWidth[0], maxMinHeight[0]);
}
@Nullable
IBinder getFragmentToken() {
return mFragmentToken;
}
@Nullable
ITaskFragmentOrganizer getTaskFragmentOrganizer() {
return mTaskFragmentOrganizer;
}
@Override
boolean isOrganized() {
return mTaskFragmentOrganizer != null;
}
/** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
final boolean isOrganizedTaskFragment() {
return mTaskFragmentOrganizer != null;
}
/** Whether the Task should be visible. */
boolean isTaskVisibleRequested() {
final Task task = getTask();
return task != null && task.isVisibleRequested();
}
boolean isReadyToTransit() {
// We only wait when this is organized to give the organizer a chance to update.
if (!isOrganizedTaskFragment()) {
return true;
}
// We don't want to start the transition if the organized TaskFragment is empty, unless
// it is requested to be removed.
if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
return true;
}
// Organizer shouldn't change embedded TaskFragment in PiP.
if (isEmbeddedTaskFragmentInPip()) {
return true;
}
// The TaskFragment becomes empty because the last running activity enters PiP when the Task
// is minimized.
if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
return true;
}
return false;
}
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
void clearLastPausedActivity() {
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
}
/**
* Sets {@link #mMinWidth} and {@link #mMinWidth} to this TaskFragment.
* It is usually set from the parent {@link Task} when adding the TaskFragment to the window
* hierarchy.
*/
void setMinDimensions(int minWidth, int minHeight) {
if (asTask() != null) {
throw new UnsupportedOperationException("This method must not be used to Task. The "
+ " minimum dimension of Task should be passed from Task constructor.");
}
mMinWidth = minWidth;
mMinHeight = minHeight;
}
/**
* Whether this is an embedded TaskFragment in PIP Task. We don't allow any client config
* override for such TaskFragment to prevent flight with PipTaskOrganizer.
*/
boolean isEmbeddedTaskFragmentInPip() {
return isOrganizedTaskFragment() && getTask() != null && getTask().inPinnedWindowingMode();
}
boolean shouldRemoveSelfOnLastChildRemoval() {
return !mCreatedByOrganizer || mIsRemovalRequested;
}
@Override
void removeChild(WindowContainer child) {
removeChild(child, true /* removeSelfIfPossible */);
}
void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
super.removeChild(child);
if (BackNavigationController.isScreenshotEnabled()) {
//TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
// implemented
ActivityRecord r = child.asActivityRecord();
if (r != null) {
mBackScreenshots.remove(r.mActivityComponent.flattenToString());
}
}
if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) {
removeImmediately("removeLastChild " + child);
}
}
/**
* Requests to remove this task fragment. If it doesn't have children, it is removed
* immediately. Otherwise it will be removed until all activities are destroyed.
*
* @param withTransition Whether to use transition animation when removing activities. Set to
* {@code false} if this is invisible to user, e.g. display removal.
*/
void remove(boolean withTransition, String reason) {
if (!hasChild()) {
removeImmediately(reason);
return;
}
mIsRemovalRequested = true;
// The task order may be changed by finishIfPossible() for adjusting focus if there are
// nested tasks, so add all activities into a list to avoid missed removals.
final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
forAllActivities((Consumer<ActivityRecord>) removingActivities::add);
for (int i = removingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = removingActivities.get(i);
if (withTransition && r.isVisible()) {
r.finishIfPossible(reason, false /* oomAdj */);
} else {
r.destroyIfPossible(reason);
}
}
}
void setDelayLastActivityRemoval(boolean delay) {
if (!mIsEmbedded) {
Slog.w(TAG, "Set delaying last activity removal on a non-embedded TF.");
}
mDelayLastActivityRemoval = delay;
}
boolean isDelayLastActivityRemoval() {
return mDelayLastActivityRemoval;
}
boolean shouldDeferRemoval() {
if (!hasChild()) {
return false;
}
return isExitAnimationRunningSelfOrChild();
}
@Override
boolean handleCompleteDeferredRemoval() {
if (shouldDeferRemoval()) {
return true;
}
return super.handleCompleteDeferredRemoval();
}
/** The overridden method must call {@link #removeImmediately()} instead of super. */
void removeImmediately(String reason) {
Slog.d(TAG, "Remove task fragment: " + reason);
removeImmediately();
}
@Override
void removeImmediately() {
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
cleanUp();
final boolean shouldExecuteAppTransition =
mClearedTaskFragmentForPip && isTaskVisibleRequested();
super.removeImmediately();
sendTaskFragmentVanished();
if (shouldExecuteAppTransition && mDisplayContent != null) {
// When the Task is still visible, and the TaskFragment is removed because the last
// running activity is reparenting to PiP, it is possible that no activity is getting
// paused or resumed (having an embedded activity in split), thus we need to relayout
// and execute it explicitly.
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
mDisplayContent.executeAppTransition();
}
}
/** Called on remove to cleanup. */
private void cleanUp() {
if (mIsEmbedded) {
mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
}
}
@Override
Dimmer getDimmer() {
// If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
if (asTask() == null) {
return mDimmer;
}
return super.getDimmer();
}
@Override
void prepareSurfaces() {
if (asTask() != null) {
super.prepareSurfaces();
return;
}
mDimmer.resetDimStates();
super.prepareSurfaces();
// Bounds need to be relative, as the dim layer is a child.
final Rect dimBounds = getBounds();
dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
if (mDimmer.updateDims(getSyncTransaction(), dimBounds)) {
scheduleAnimation();
}
}
@Override
boolean canBeAnimationTarget() {
return true;
}
@Override
boolean fillsParent() {
// From the perspective of policy, we still want to report that this task fills parent
// in fullscreen windowing mode even it doesn't match parent bounds because there will be
// letterbox around its real content.
return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
}
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
sb.setLength(sb.length() - 1); // Remove tail '}'.
if (mTaskFragmentOrganizerUid != INVALID_UID) {
sb.append(" organizerUid=");
sb.append(mTaskFragmentOrganizerUid);
}
if (mTaskFragmentOrganizerProcessName != null) {
sb.append(" organizerProc=");
sb.append(mTaskFragmentOrganizerProcessName);
}
if (mAdjacentTaskFragment != null) {
sb.append(" adjacent=");
sb.append(mAdjacentTaskFragment);
}
sb.append('}');
return sb.toString();
}
@Override
public String toString() {
return "TaskFragment{" + Integer.toHexString(System.identityHashCode(this))
+ " mode=" + WindowConfiguration.windowingModeToString(getWindowingMode()) + "}";
}
boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
boolean printed = false;
Runnable headerPrinter = () -> {
if (needSep) {
pw.println();
}
if (header != null) {
header.run();
}
dumpInner(prefix, pw, dumpAll, dumpPackage);
};
if (dumpPackage == null) {
// If we are not filtering by package, we want to print absolutely everything,
// so always print the header even if there are no tasks/activities inside.
headerPrinter.run();
headerPrinter = null;
printed = true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
WindowContainer child = mChildren.get(i);
if (child.asTaskFragment() != null) {
printed |= child.asTaskFragment().dump(prefix + " ", fd, pw, dumpAll,
dumpClient, dumpPackage, needSep, headerPrinter);
} else if (child.asActivityRecord() != null) {
ActivityRecord.dumpActivity(fd, pw, i, child.asActivityRecord(), prefix + " ",
"Hist ", true, !dumpAll, dumpClient, dumpPackage, false, headerPrinter,
getTask());
}
}
return printed;
}
void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
pw.print(prefix); pw.print("* "); pw.println(toFullString());
final Rect bounds = getRequestedOverrideBounds();
if (!bounds.isEmpty()) {
pw.println(prefix + " mBounds=" + bounds);
}
if (mIsRemovalRequested) {
pw.println(prefix + " mIsRemovalRequested=true");
}
if (dumpAll) {
printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
prefix + " mLastPausedActivity: ", null);
}
}
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);
pw.println(prefix + "bounds=" + getBounds().toShortString());
final String doublePrefix = prefix + " ";
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowContainer<?> child = mChildren.get(i);
final TaskFragment tf = child.asTaskFragment();
pw.println(prefix + "* " + (tf != null ? tf.toFullString() : child));
// Only dump non-activity because full activity info is already printed by
// RootWindowContainer#dumpActivities.
if (tf != null) {
child.dump(pw, doublePrefix, dumpAll);
}
}
}
@Override
void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
final ActivityRecord topActivity = topRunningActivity();
proto.write(USER_ID, topActivity != null ? topActivity.mUserId : USER_NULL);
proto.write(TITLE, topActivity != null ? topActivity.intent.getComponent()
.flattenToShortString() : "TaskFragment");
proto.end(token);
}
@Override
long getProtoFieldId() {
return TASK_FRAGMENT;
}
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
return;
}
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(DISPLAY_ID, getDisplayId());
proto.write(ACTIVITY_TYPE, getActivityType());
proto.write(MIN_WIDTH, mMinWidth);
proto.write(MIN_HEIGHT, mMinHeight);
proto.end(token);
}
}