blob: 9cc311dc6c8e65efbfe2656c94fa5fa5bd90bccc [file] [log] [blame]
/*
* Copyright (C) 2019 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 com.android.server.wm.ActivityRecord.State.INITIALIZING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.Task.TAG_VISIBILITY;
import android.annotation.Nullable;
import android.util.Slog;
import java.util.ArrayList;
/** Helper class to ensure activities are in the right visible state for a container. */
class EnsureActivitiesVisibleHelper {
private final TaskFragment mTaskFragment;
private ActivityRecord mTopRunningActivity;
private ActivityRecord mStarting;
private boolean mAboveTop;
private boolean mContainerShouldBeVisible;
private boolean mBehindFullyOccludedContainer;
private int mConfigChanges;
private boolean mPreserveWindows;
private boolean mNotifyClients;
EnsureActivitiesVisibleHelper(TaskFragment container) {
mTaskFragment = container;
}
/**
* Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
*
* @param starting The activity that is being started
* @param configChanges Parts of the configuration that changed for this activity for evaluating
* if the screen should be frozen.
* @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
mStarting = starting;
mTopRunningActivity = mTaskFragment.topRunningActivity();
// If the top activity is not fullscreen, then we need to make sure any activities under it
// are now visible.
mAboveTop = mTopRunningActivity != null;
mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
mConfigChanges = configChanges;
mPreserveWindows = preserveWindows;
mNotifyClients = notifyClients;
}
/**
* Update and commit visibility with an option to also update the configuration of visible
* activities.
* @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
*
* @param configChanges Parts of the configuration that changed for this activity for evaluating
* if the screen should be frozen.
* @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
reset(starting, configChanges, preserveWindows, notifyClients);
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity
+ " configChanges=0x" + Integer.toHexString(configChanges));
}
if (mTopRunningActivity != null && mTaskFragment.asTask() != null) {
// TODO(14709632): Check if this needed to be implemented in TaskFragment.
mTaskFragment.asTask().checkTranslucentActivityWaiting(mTopRunningActivity);
}
// We should not resume activities that being launched behind because these
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTopRunningActivity != null
&& !mTopRunningActivity.mLaunchTaskBehind
&& mTaskFragment.canBeResumed(starting)
&& (starting == null || !starting.isDescendantOf(mTaskFragment));
ArrayList<TaskFragment> adjacentTaskFragments = null;
for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mTaskFragment.mChildren.get(i);
final TaskFragment childTaskFragment = child.asTaskFragment();
if (childTaskFragment != null
&& childTaskFragment.getTopNonFinishingActivity() != null) {
childTaskFragment.updateActivityVisibilities(starting, configChanges,
preserveWindows, notifyClients);
// The TaskFragment should fully occlude the activities below if the bounds
// equals to its parent task, unless it is translucent.
mBehindFullyOccludedContainer |=
(childTaskFragment.getBounds().equals(mTaskFragment.getBounds())
&& !childTaskFragment.isTranslucent(starting));
if (mAboveTop && mTopRunningActivity.getTaskFragment() == childTaskFragment) {
mAboveTop = false;
}
if (mBehindFullyOccludedContainer) {
continue;
}
if (adjacentTaskFragments != null && adjacentTaskFragments.contains(
childTaskFragment)) {
if (!childTaskFragment.isTranslucent(starting)
&& !childTaskFragment.getAdjacentTaskFragment().isTranslucent(
starting)) {
// Everything behind two adjacent TaskFragments are occluded.
mBehindFullyOccludedContainer = true;
}
continue;
}
final TaskFragment adjacentTaskFrag = childTaskFragment.getAdjacentTaskFragment();
if (adjacentTaskFrag != null) {
if (adjacentTaskFragments == null) {
adjacentTaskFragments = new ArrayList<>();
}
adjacentTaskFragments.add(adjacentTaskFrag);
}
} else if (child.asActivityRecord() != null) {
setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
}
}
}
private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
final boolean resumeTopActivity) {
final boolean isTop = r == mTopRunningActivity;
if (mAboveTop && !isTop) {
// Ensure activities above the top-running activity to be invisible because the
// activity should be finishing or cannot show to current user.
r.makeInvisible();
return;
}
mAboveTop = false;
r.updateVisibilityIgnoringKeyguard(mBehindFullyOccludedContainer);
final boolean reallyVisible = r.shouldBeVisibleUnchecked();
// Check whether activity should be visible without Keyguard influence
if (r.visibleIgnoringKeyguard) {
if (r.occludesParent()) {
// At this point, nothing else needs to be shown in this task.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+ " containerVisible=" + mContainerShouldBeVisible
+ " behindFullyOccluded=" + mBehindFullyOccludedContainer);
}
mBehindFullyOccludedContainer = true;
} else {
mBehindFullyOccludedContainer = false;
}
} else if (r.isState(INITIALIZING)) {
r.cancelInitializing();
}
if (reallyVisible) {
if (r.finishing) {
return;
}
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Make visible? " + r
+ " finishing=" + r.finishing + " state=" + r.getState());
}
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != mStarting && mNotifyClients) {
r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
true /* ignoreVisibility */);
}
if (!r.attachedToProcess()) {
makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
resumeTopActivity && isTop, r);
} else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
}
if (r.mClientVisibilityDeferred && mNotifyClients) {
r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
r.mClientVisibilityDeferred = false;
}
r.handleAlreadyVisible();
if (mNotifyClients) {
r.makeActiveIfNeeded(mStarting);
}
} else {
r.makeVisibleIfNeeded(mStarting, mNotifyClients);
}
// Aggregate current change flags.
mConfigChanges |= r.configChangeFlags;
} else {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+ " finishing=" + r.finishing + " state=" + r.getState()
+ " containerShouldBeVisible=" + mContainerShouldBeVisible
+ " behindFullyOccludedContainer=" + mBehindFullyOccludedContainer
+ " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
}
r.makeInvisible();
}
if (!mBehindFullyOccludedContainer && mTaskFragment.isActivityTypeHome()
&& r.isRootOfTask()) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Home task: at " + mTaskFragment
+ " containerShouldBeVisible=" + mContainerShouldBeVisible
+ " behindOccludedParentContainer=" + mBehindFullyOccludedContainer);
}
// No other task in the root home task should be visible behind the home activity.
// Home activities is usually a translucent activity with the wallpaper behind
// them. However, when they don't have the wallpaper behind them, we want to
// show activities in the next application root task behind them vs. another
// task in the root home task like recents.
mBehindFullyOccludedContainer = true;
}
}
private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
boolean andResume, ActivityRecord r) {
// This activity needs to be visible, but isn't even running...
// get it started and resume if no other root task in this root task is resumed.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
}
if (r != starting) {
r.startFreezingScreenLocked(configChanges);
}
if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
}
r.setVisibility(true);
}
if (r != starting) {
mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume,
true /* checkConfig */);
}
}
}