blob: d94e8e426c4bb2cec95b58f6906e0e3507057b26 [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 androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize
* task fragments.
*
* All calls into methods of this class are expected to be on the UI thread.
*/
class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
/** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
@VisibleForTesting
final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
@NonNull
private final TaskFragmentCallback mCallback;
@VisibleForTesting
@Nullable
TaskFragmentAnimationController mAnimationController;
/**
* Callback that notifies the controller about changes to task fragments.
*/
interface TaskFragmentCallback {
void onTransactionReady(@NonNull TaskFragmentTransaction transaction);
}
/**
* @param executor callbacks from WM Core are posted on this executor. It should be tied to the
* UI thread that all other calls into methods of this class are also on.
*/
JetpackTaskFragmentOrganizer(@NonNull Executor executor,
@NonNull TaskFragmentCallback callback) {
super(executor);
mCallback = callback;
}
@Override
public void unregisterOrganizer() {
if (mAnimationController != null) {
mAnimationController.unregisterRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer();
}
/**
* Overrides the animation for transitions of embedded activities organized by this organizer.
*/
void overrideSplitAnimation() {
if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this);
}
mAnimationController.registerRemoteAnimations();
}
/**
* Starts a new Activity and puts it into split with an existing Activity side-by-side.
* @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will
* be resized based on {@param launchingFragmentBounds}.
* Otherwise, we will create a new TaskFragment with the given
* token for the {@param launchingActivity}.
* @param launchingRelBounds the initial relative bounds for the launching TaskFragment.
* @param launchingActivity the Activity to put on the left hand side of the split as the
* primary.
* @param secondaryFragmentToken token to create the secondary TaskFragment with.
* @param secondaryRelBounds the initial relative bounds for the secondary TaskFragment
* @param activityIntent Intent to start the secondary Activity with.
* @param activityOptions ActivityOptions to start the secondary Activity with.
* @param windowingMode the windowing mode to set for the TaskFragments.
* @param splitAttributes the {@link SplitAttributes} to represent the split.
*/
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull IBinder launchingFragmentToken, @NonNull Rect launchingRelBounds,
@NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
@NonNull Rect secondaryRelBounds, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
@WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
if (mFragmentInfos.containsKey(launchingFragmentToken)) {
resizeTaskFragment(wct, launchingFragmentToken, launchingRelBounds);
updateWindowingMode(wct, launchingFragmentToken, windowingMode);
} else {
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
launchingRelBounds, windowingMode, launchingActivity);
}
updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
// Create a TaskFragment for the secondary activity.
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), secondaryFragmentToken, ownerToken)
.setInitialRelativeBounds(secondaryRelBounds)
.setWindowingMode(windowingMode)
// Make sure to set the paired fragment token so that the new TaskFragment will be
// positioned right above the paired TaskFragment.
// This is needed in case we need to launch a placeholder Activity to split below a
// transparent always-expand Activity.
.setPairedPrimaryFragmentToken(launchingFragmentToken)
.build();
createTaskFragment(wct, fragmentOptions);
updateAnimationParams(wct, secondaryFragmentToken, splitAttributes);
wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragmentsWithRule(wct, launchingFragmentToken, secondaryFragmentToken, rule);
setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
false /* isStacked */);
}
/**
* Expands an existing TaskFragment to fill parent.
* @param wct WindowContainerTransaction in which the task fragment should be resized.
* @param fragmentToken token of an existing TaskFragment.
*/
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
clearAdjacentTaskFragments(wct, fragmentToken);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
* Expands an Activity to fill parent by moving it to a new TaskFragment.
* @param fragmentToken token to create new TaskFragment with.
* @param activity activity to move to the fill-parent TaskFragment.
*/
void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull Activity activity) {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect relBounds,
@WindowingMode int windowingMode) {
createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode,
null /* pairedActivityToken */);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
* @param pairedActivityToken The token of the activity that will be reparented to this task
* fragment. When it is not {@code null}, the task fragment will be
* positioned right above it.
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect relBounds, @WindowingMode int windowingMode,
@Nullable IBinder pairedActivityToken) {
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
getOrganizerToken(), fragmentToken, ownerToken)
.setInitialRelativeBounds(relBounds)
.setWindowingMode(windowingMode)
.setPairedActivityToken(pairedActivityToken)
.build();
createTaskFragment(wct, fragmentOptions);
}
void createTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentCreationParams fragmentOptions) {
if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken="
+ fragmentOptions.getFragmentToken());
}
wct.createTaskFragment(fragmentOptions);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relBounds,
@WindowingMode int windowingMode, @NonNull Activity activity) {
final IBinder reparentActivityToken = activity.getActivityToken();
createTaskFragment(wct, fragmentToken, ownerToken, relBounds, windowingMode,
reparentActivityToken);
wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
}
/**
* Sets the two given TaskFragments as adjacent to each other with respecting the given
* {@link SplitRule} for {@link WindowContainerTransaction.TaskFragmentAdjacentParams}.
*/
void setAdjacentTaskFragmentsWithRule(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
final boolean finishSecondaryWithPrimary =
SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
final boolean finishPrimaryWithSecondary =
SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
}
setAdjacentTaskFragments(wct, primary, secondary, adjacentParams);
}
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary,
@Nullable WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams) {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
void clearAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
// Clear primary will also clear secondary.
wct.clearAdjacentTaskFragments(fragmentToken);
}
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
boolean isStacked) {
final boolean finishPrimaryWithSecondary;
if (isStacked) {
finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
getFinishPrimaryWithSecondaryBehavior(splitRule));
} else {
finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
}
setCompanionTaskFragment(wct, primary, finishPrimaryWithSecondary ? secondary : null);
final boolean finishSecondaryWithPrimary;
if (isStacked) {
finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
getFinishSecondaryWithPrimaryBehavior(splitRule));
} else {
finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
}
setCompanionTaskFragment(wct, secondary, finishSecondaryWithPrimary ? primary : null);
}
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
@Nullable IBinder secondary) {
wct.setCompanionTaskFragment(primary, secondary);
}
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect relBounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
}
if (relBounds == null) {
relBounds = new Rect();
}
wct.setRelativeBounds(mFragmentInfos.get(fragmentToken).getToken(), relBounds);
}
void updateWindowingMode(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
}
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
/**
* Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on
* {@link SplitAttributes}.
*/
void updateAnimationParams(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) {
updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes));
}
void updateAnimationParams(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
OP_TYPE_SET_ANIMATION_PARAMS)
.setAnimationParams(animationParams)
.build();
wct.addTaskFragmentOperation(fragmentToken, operation);
}
void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
wct.deleteTaskFragment(fragmentToken);
}
void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
}
void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
}
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
mCallback.onTransactionReady(transaction);
}
private static TaskFragmentAnimationParams createAnimationParamsOrDefault(
@Nullable SplitAttributes splitAttributes) {
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
return new TaskFragmentAnimationParams.Builder()
.setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
.build();
}
}