blob: 707f9fc9ea5fd1c3c85124362cf564b17cbca76f [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.window.TaskFragmentOrganizer.putErrorInfoInBundle;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.RemoteTransition;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
/**
* Stores and manages the client {@link android.window.TaskFragmentOrganizer}.
*/
public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerController.Stub {
private static final String TAG = "TaskFragmentOrganizerController";
private static final long TEMPORARY_ACTIVITY_TOKEN_TIMEOUT_MS = 5000;
private final ActivityTaskManagerService mAtmService;
private final WindowManagerGlobalLock mGlobalLock;
private final WindowOrganizerController mWindowOrganizerController;
/**
* A Map which manages the relationship between
* {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState}
*/
private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState =
new ArrayMap<>();
/**
* Map from {@link ITaskFragmentOrganizer} to a list of related {@link PendingTaskFragmentEvent}
*/
private final ArrayMap<IBinder, List<PendingTaskFragmentEvent>> mPendingTaskFragmentEvents =
new ArrayMap<>();
private final ArraySet<Task> mTmpTaskSet = new ArraySet<>();
TaskFragmentOrganizerController(@NonNull ActivityTaskManagerService atm,
@NonNull WindowOrganizerController windowOrganizerController) {
mAtmService = requireNonNull(atm);
mGlobalLock = atm.mGlobalLock;
mWindowOrganizerController = requireNonNull(windowOrganizerController);
}
/**
* A class to manage {@link ITaskFragmentOrganizer} and its organized
* {@link TaskFragment TaskFragments}.
*/
private class TaskFragmentOrganizerState implements IBinder.DeathRecipient {
private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>();
private final ITaskFragmentOrganizer mOrganizer;
private final int mOrganizerPid;
private final int mOrganizerUid;
/**
* Map from {@link TaskFragment} to the last {@link TaskFragmentInfo} sent to the
* organizer.
*/
private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos =
new WeakHashMap<>();
/**
* Map from {@link TaskFragment} to its leaf {@link Task#mTaskId}. Embedded
* {@link TaskFragment} will not be reparented until it is removed.
*/
private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>();
/**
* Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the
* organizer.
*/
private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos =
new SparseArray<>();
/**
* Map from temporary activity token to the corresponding {@link ActivityRecord}.
*/
private final Map<IBinder, ActivityRecord> mTemporaryActivityTokens =
new WeakHashMap<>();
/**
* Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true,
* the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the
* client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event.
*/
private final boolean mIsSystemOrganizer;
/**
* {@link RemoteAnimationDefinition} for embedded activities transition animation that is
* organized by this organizer.
*/
@Nullable
private RemoteAnimationDefinition mRemoteAnimationDefinition;
/**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
* {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
* wait until the organizer finished handling the {@link TaskFragmentTransaction}.
* @see #onTransactionFinished(IBinder)
*/
private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
/**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to a
* {@link Transition.ReadyCondition} that is waiting for the {@link TaskFragmentTransaction}
* to complete.
* @see #onTransactionHandled
*/
private final ArrayMap<IBinder, Transition.ReadyCondition> mInFlightTransactions =
new ArrayMap<>();
TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid,
boolean isSystemOrganizer) {
mOrganizer = organizer;
mOrganizerPid = pid;
mOrganizerUid = uid;
mIsSystemOrganizer = isSystemOrganizer;
try {
mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
} catch (RemoteException e) {
Slog.e(TAG, "TaskFragmentOrganizer failed to register death recipient");
}
}
@Override
public void binderDied() {
synchronized (mGlobalLock) {
removeOrganizer(mOrganizer, "client died");
}
}
/**
* @return {@code true} if taskFragment is organized and not sent the appeared event before.
*/
boolean addTaskFragment(TaskFragment taskFragment) {
if (taskFragment.mTaskFragmentAppearedSent) {
return false;
}
if (mOrganizedTaskFragments.contains(taskFragment)) {
return false;
}
mOrganizedTaskFragments.add(taskFragment);
return true;
}
void removeTaskFragment(TaskFragment taskFragment) {
mOrganizedTaskFragments.remove(taskFragment);
}
void dispose(@NonNull String reason) {
boolean wasVisible = false;
for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(i);
if (taskFragment.isVisibleRequested()) {
wasVisible = true;
}
// Cleanup the TaskFragmentOrganizer from all TaskFragments it organized before
// removing the windows to prevent it from adding any additional TaskFragment
// pending event.
taskFragment.onTaskFragmentOrganizerRemoved();
}
final TransitionController transitionController = mAtmService.getTransitionController();
if (wasVisible && transitionController.isShellTransitionsEnabled()
&& !transitionController.isCollecting()) {
final Task task = mOrganizedTaskFragments.get(0).getTask();
final boolean containsNonEmbeddedActivity =
task != null && task.getActivity(a -> !a.isEmbedded()) != null;
transitionController.requestStartTransition(
transitionController.createTransition(WindowManager.TRANSIT_CLOSE),
// The task will be removed if all its activities are embedded, then the
// task is the trigger.
containsNonEmbeddedActivity ? null : task,
null /* remoteTransition */, null /* displayChange */);
}
// Defer to avoid unnecessary layout when there are multiple TaskFragments removal.
mAtmService.deferWindowLayout();
try {
while (!mOrganizedTaskFragments.isEmpty()) {
final TaskFragment taskFragment = mOrganizedTaskFragments.remove(0);
taskFragment.removeImmediately();
}
} finally {
mAtmService.continueWindowLayout();
}
for (int i = mDeferredTransitions.size() - 1; i >= 0; i--) {
// Cleanup any running transaction to unblock the current transition.
onTransactionFinished(mDeferredTransitions.keyAt(i));
}
for (int i = mInFlightTransactions.size() - 1; i >= 0; i--) {
// Cleanup any in-flight transactions to unblock the transition.
mInFlightTransactions.valueAt(i).meetAlternate("disposed(" + reason + ")");
}
mOrganizer.asBinder().unlinkToDeath(this, 0 /* flags */);
}
@NonNull
TaskFragmentTransaction.Change prepareTaskFragmentAppeared(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final int taskId = tf.getTask().mTaskId;
tf.mTaskFragmentAppearedSent = true;
mLastSentTaskFragmentInfos.put(tf, info);
mTaskFragmentTaskIds.put(tf, taskId);
final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_APPEARED)
.setTaskFragmentToken(tf.getFragmentToken())
.setTaskFragmentInfo(info)
.setTaskId(taskId);
if (mIsSystemOrganizer) {
change.setTaskFragmentSurfaceControl(tf.getSurfaceControl());
}
return change;
}
@NonNull
TaskFragmentTransaction.Change prepareTaskFragmentVanished(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
tf.mTaskFragmentAppearedSent = false;
mLastSentTaskFragmentInfos.remove(tf);
// Cleanup TaskFragmentParentConfig if this is the last TaskFragment in the Task.
final int taskId;
if (mTaskFragmentTaskIds.containsKey(tf)) {
taskId = mTaskFragmentTaskIds.remove(tf);
if (!mTaskFragmentTaskIds.containsValue(taskId)) {
// No more TaskFragment in the Task.
mLastSentTaskFragmentParentInfos.remove(taskId);
}
} else {
// This can happen if the appeared wasn't sent before remove.
taskId = INVALID_TASK_ID;
}
return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED)
.setTaskFragmentToken(tf.getFragmentToken())
.setTaskFragmentInfo(tf.getTaskFragmentInfo())
.setTaskId(taskId);
}
@Nullable
TaskFragmentTransaction.Change prepareTaskFragmentInfoChanged(
@NonNull TaskFragment tf) {
// Check if the info is different from the last reported info.
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
info.getConfiguration(), lastInfo.getConfiguration())) {
return null;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s",
tf.getName());
mLastSentTaskFragmentInfos.put(tf, info);
return new TaskFragmentTransaction.Change(
TYPE_TASK_FRAGMENT_INFO_CHANGED)
.setTaskFragmentToken(tf.getFragmentToken())
.setTaskFragmentInfo(info)
.setTaskId(tf.getTask().mTaskId);
}
@Nullable
TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) {
final int taskId = task.mTaskId;
// Check if the parent info is different from the last reported parent info.
final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo();
final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos
.get(taskId);
final Configuration lastParentConfig = lastParentInfo != null
? lastParentInfo.getConfiguration() : null;
if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo)
&& configurationsAreEqualForOrganizer(parentInfo.getConfiguration(),
lastParentConfig)) {
return null;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"TaskFragment parent info changed name=%s parentTaskId=%d",
task.getName(), taskId);
mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo));
return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
.setTaskId(taskId)
.setTaskFragmentParentInfo(parentInfo);
}
@NonNull
TaskFragmentTransaction.Change prepareTaskFragmentError(
@Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
@TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
final TaskFragmentInfo info =
taskFragment != null ? taskFragment.getTaskFragmentInfo() : null;
final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType);
return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR)
.setErrorCallbackToken(errorCallbackToken)
.setErrorBundle(errorBundle);
}
@Nullable
TaskFragmentTransaction.Change prepareActivityReparentedToTask(
@NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
return null;
}
final Task task = activity.getTask();
if (task == null || task.effectiveUid != mOrganizerUid) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not in a task belong to the organizer app.");
return null;
}
if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
|| !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded in trusted mode.");
return null;
}
final IBinder activityToken;
if (activity.getPid() == mOrganizerPid) {
// We only pass the actual token if the activity belongs to the organizer process.
activityToken = activity.token;
} else {
// For security, we can't pass the actual token if the activity belongs to a
// different process. In this case, we will pass a temporary token that organizer
// can use to reparent through WindowContainerTransaction.
activityToken = new Binder("TemporaryActivityToken");
mTemporaryActivityTokens.put(activityToken, activity);
final Runnable timeout = () -> {
synchronized (mGlobalLock) {
mTemporaryActivityTokens.remove(activityToken);
}
};
mAtmService.mWindowManager.mH.postDelayed(timeout,
TEMPORARY_ACTIVITY_TOKEN_TIMEOUT_MS);
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
.setActivityIntent(trimIntent(activity.intent))
.setActivityToken(activityToken);
}
void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) {
if (transaction.isEmpty()) {
return;
}
try {
mOrganizer.onTransactionReady(transaction);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
return;
}
if (!mWindowOrganizerController.getTransitionController().isCollecting()) {
return;
}
final int transitionId = mWindowOrganizerController.getTransitionController()
.getCollectingTransitionId();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Defer transition id=%d for TaskFragmentTransaction=%s", transitionId,
transaction.getTransactionToken());
mDeferredTransitions.put(transaction.getTransactionToken(), transitionId);
mWindowOrganizerController.getTransitionController().deferTransitionReady();
final Transition.ReadyCondition transactionApplied = new Transition.ReadyCondition(
"task-fragment transaction", transaction);
mWindowOrganizerController.getTransitionController().waitFor(transactionApplied);
mInFlightTransactions.put(transaction.getTransactionToken(), transactionApplied);
}
/** Called when the transaction is finished. */
void onTransactionFinished(@NonNull IBinder transactionToken) {
if (!mDeferredTransitions.containsKey(transactionToken)) {
return;
}
final int transitionId = mDeferredTransitions.remove(transactionToken);
if (!mWindowOrganizerController.getTransitionController().isCollecting()
|| mWindowOrganizerController.getTransitionController()
.getCollectingTransitionId() != transitionId) {
// This can happen when the transition is timeout or abort.
ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Deferred transition id=%d has been continued before the"
+ " TaskFragmentTransaction=%s is finished",
transitionId, transactionToken);
return;
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Continue transition id=%d for TaskFragmentTransaction=%s", transitionId,
transactionToken);
mWindowOrganizerController.getTransitionController().continueTransitionReady();
}
}
@Nullable
ActivityRecord getReparentActivityFromTemporaryToken(
@Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder activityToken) {
if (organizer == null || activityToken == null) {
return null;
}
final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
organizer.asBinder());
return state != null
? state.mTemporaryActivityTokens.remove(activityToken)
: null;
}
@VisibleForTesting
void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
registerOrganizerInternal(organizer, false /* isSystemOrganizer */);
}
@Override
public void registerOrganizer(
@NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
registerOrganizerInternal(
organizer,
Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer);
}
@VisibleForTesting
void registerOrganizerInternal(
@NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) {
if (isSystemOrganizer) {
enforceTaskPermission("registerSystemOrganizer()");
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Register task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
if (isOrganizerRegistered(organizer)) {
throw new IllegalStateException(
"Replacing existing organizer currently unsupported");
}
mTaskFragmentOrganizerState.put(organizer.asBinder(),
new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer));
mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>());
}
}
@Override
public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Unregister task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
removeOrganizer(organizer, "unregistered");
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
@NonNull RemoteAnimationDefinition definition) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Register remote animations for organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (organizerState == null) {
throw new IllegalStateException("The organizer hasn't been registered.");
}
if (organizerState.mRemoteAnimationDefinition != null) {
throw new IllegalStateException(
"The organizer has already registered remote animations="
+ organizerState.mRemoteAnimationDefinition);
}
definition.setCallingPidUid(pid, uid);
organizerState.mRemoteAnimationDefinition = definition;
}
}
@Override
public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Unregister remote animations for organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (organizerState == null) {
Slog.e(TAG, "The organizer hasn't been registered.");
return;
}
organizerState.mRemoteAnimationDefinition = null;
}
}
@Override
public void onTransactionHandled(@NonNull IBinder transactionToken,
@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
if (isValidTransaction(wct)) {
applyTransaction(
wct, transitionType, shouldApplyIndependently, null /* remoteTransition */);
}
// Even if the transaction is empty, we still need to invoke #onTransactionFinished
// unless the organizer has been unregistered.
final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
final TaskFragmentOrganizerState state = organizer != null
? mTaskFragmentOrganizerState.get(organizer.asBinder())
: null;
if (state != null) {
state.onTransactionFinished(transactionToken);
final Transition.ReadyCondition condition =
state.mInFlightTransactions.remove(transactionToken);
if (condition != null) {
condition.meet();
}
}
}
}
@Override
public void applyTransaction(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently,
@Nullable RemoteTransition remoteTransition) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
if (!isValidTransaction(wct)) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
shouldApplyIndependently, remoteTransition);
}
}
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
* {@code null} if it doesn't.
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
@NonNull ITaskFragmentOrganizer organizer) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (organizerState == null) {
Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
+ " to play animation on its organized windows.");
return null;
}
return organizerState.mRemoteAnimationDefinition;
}
}
int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
}
void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
if (taskFragment.mTaskFragmentVanishedSent) {
return;
}
if (taskFragment.getTask() == null) {
Slog.w(TAG, "onTaskFragmentAppeared failed because it is not attached tf="
+ taskFragment);
return;
}
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
if (!state.addTaskFragment(taskFragment)) {
return;
}
PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_APPEARED);
if (pendingEvent == null) {
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_APPEARED, organizer)
.setTaskFragment(taskFragment)
.build());
}
}
void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
if (taskFragment.mTaskFragmentVanishedSent) {
return;
}
validateAndGetState(organizer);
if (!taskFragment.mTaskFragmentAppearedSent) {
// Skip if TaskFragment still not appeared.
return;
}
PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment);
if (pendingEvent == null) {
pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_INFO_CHANGED, organizer)
.setTaskFragment(taskFragment)
.build();
} else {
// Remove and add for re-ordering.
removePendingEvent(pendingEvent);
// Reset the defer time when TaskFragment is changed, so that it can check again if
// the event should be sent to the organizer, for example the TaskFragment may become
// empty.
pendingEvent.mDeferTime = 0;
}
addPendingEvent(pendingEvent);
}
void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer,
@NonNull TaskFragment taskFragment) {
if (taskFragment.mTaskFragmentVanishedSent) {
return;
}
taskFragment.mTaskFragmentVanishedSent = true;
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
final List<PendingTaskFragmentEvent> pendingEvents = mPendingTaskFragmentEvents
.get(organizer.asBinder());
// Remove any pending events since this TaskFragment is being removed.
for (int i = pendingEvents.size() - 1; i >= 0; i--) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
if (taskFragment == event.mTaskFragment) {
pendingEvents.remove(i);
}
}
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_VANISHED, organizer)
.setTaskFragment(taskFragment)
.build());
state.removeTaskFragment(taskFragment);
// Make sure the vanished event will be dispatched if there are no other changes.
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
void onTaskFragmentError(@NonNull ITaskFragmentOrganizer organizer,
@Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
@TaskFragmentOperation.OperationType int opType, @NonNull Throwable exception) {
if (taskFragment != null && taskFragment.mTaskFragmentVanishedSent) {
return;
}
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
.setTaskFragment(taskFragment)
.setException(exception)
.setOpType(opType)
.build());
// Make sure the error event will be dispatched if there are no other changes.
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
organizer = activity.mLastTaskFragmentOrganizerBeforePip;
} else {
// Find the topmost TaskFragmentOrganizer.
final Task task = activity.getTask();
final TaskFragment[] organizedTf = new TaskFragment[1];
task.forAllLeafTaskFragments(tf -> {
if (tf.isOrganizedTaskFragment()) {
organizedTf[0] = tf;
return true;
}
return false;
});
if (organizedTf[0] == null) {
return;
}
organizer = organizedTf[0].getTaskFragmentOrganizer();
}
if (!isOrganizerRegistered(organizer)) {
Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
return;
}
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
.setActivity(activity)
.build());
}
void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
@NonNull Task task) {
validateAndGetState(organizer);
final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent(
organizer, task);
if (pendingEvent == null) {
addPendingEvent(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
.setTask(task)
.build());
}
// Make sure the parent info changed event will be dispatched if there are no other changes.
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(organizerToken);
return state != null && state.mIsSystemOrganizer;
}
@Nullable
private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
@NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
.get(organizer.asBinder());
for (int i = events.size() - 1; i >= 0; i--) {
final PendingTaskFragmentEvent event = events.get(i);
if (task == event.mTask
&& event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
return event;
}
}
return null;
}
private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) {
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event);
}
private void removePendingEvent(@NonNull PendingTaskFragmentEvent event) {
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
}
private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer,
@NonNull String reason) {
final TaskFragmentOrganizerState state = mTaskFragmentOrganizerState.get(
organizer.asBinder());
if (state == null) {
Slog.w(TAG, "The organizer has already been removed.");
return;
}
// Remove any pending event of this organizer first because state.dispose() may trigger
// event dispatch as result of surface placement.
mPendingTaskFragmentEvents.remove(organizer.asBinder());
// remove all of the children of the organized TaskFragment
state.dispose(reason);
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
/**
* Makes sure that the organizer has been correctly registered to prevent any Sidecar
* implementation from organizing {@link TaskFragment} without registering first. In such case,
* we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the
* {@link TaskFragment} after the organizer process died.
*/
@NonNull
private TaskFragmentOrganizerState validateAndGetState(
@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (state == null) {
throw new IllegalArgumentException(
"TaskFragmentOrganizer has not been registered. Organizer=" + organizer);
}
return state;
}
boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
if (t.isEmpty()) {
return false;
}
final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
// Transaction from an unregistered organizer should not be applied. This can happen
// when the organizer process died before the transaction is applied.
Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
return false;
}
return true;
}
/**
* A class to store {@link ITaskFragmentOrganizer} and its organized
* {@link TaskFragment TaskFragments} with different pending event request.
*/
private static class PendingTaskFragmentEvent {
static final int EVENT_APPEARED = 0;
static final int EVENT_VANISHED = 1;
static final int EVENT_INFO_CHANGED = 2;
static final int EVENT_PARENT_INFO_CHANGED = 3;
static final int EVENT_ERROR = 4;
static final int EVENT_ACTIVITY_REPARENTED_TO_TASK = 5;
@IntDef(prefix = "EVENT_", value = {
EVENT_APPEARED,
EVENT_VANISHED,
EVENT_INFO_CHANGED,
EVENT_PARENT_INFO_CHANGED,
EVENT_ERROR,
EVENT_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@EventType
private final int mEventType;
private final ITaskFragmentOrganizer mTaskFragmentOrg;
@Nullable
private final TaskFragment mTaskFragment;
@Nullable
private final IBinder mErrorCallbackToken;
@Nullable
private final Throwable mException;
@Nullable
private final ActivityRecord mActivity;
@Nullable
private final Task mTask;
// Set when the event is deferred due to the host task is invisible. The defer time will
// be the last active time of the host task.
private long mDeferTime;
@TaskFragmentOperation.OperationType
private int mOpType;
private PendingTaskFragmentEvent(@EventType int eventType,
ITaskFragmentOrganizer taskFragmentOrg,
@Nullable TaskFragment taskFragment,
@Nullable IBinder errorCallbackToken,
@Nullable Throwable exception,
@Nullable ActivityRecord activity,
@Nullable Task task,
@TaskFragmentOperation.OperationType int opType) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
mTaskFragment = taskFragment;
mErrorCallbackToken = errorCallbackToken;
mException = exception;
mActivity = activity;
mTask = task;
mOpType = opType;
}
/**
* @return {@code true} if the pending event is related with taskFragment created, vanished
* and information changed.
*/
boolean isLifecycleEvent() {
switch (mEventType) {
case EVENT_APPEARED:
case EVENT_VANISHED:
case EVENT_INFO_CHANGED:
case EVENT_PARENT_INFO_CHANGED:
return true;
default:
return false;
}
}
private static class Builder {
@EventType
private final int mEventType;
private final ITaskFragmentOrganizer mTaskFragmentOrg;
@Nullable
private TaskFragment mTaskFragment;
@Nullable
private IBinder mErrorCallbackToken;
@Nullable
private Throwable mException;
@Nullable
private ActivityRecord mActivity;
@Nullable
private Task mTask;
@TaskFragmentOperation.OperationType
private int mOpType;
Builder(@EventType int eventType, @NonNull ITaskFragmentOrganizer taskFragmentOrg) {
mEventType = eventType;
mTaskFragmentOrg = requireNonNull(taskFragmentOrg);
}
Builder setTaskFragment(@Nullable TaskFragment taskFragment) {
mTaskFragment = taskFragment;
return this;
}
Builder setErrorCallbackToken(@Nullable IBinder errorCallbackToken) {
mErrorCallbackToken = errorCallbackToken;
return this;
}
Builder setException(@NonNull Throwable exception) {
mException = requireNonNull(exception);
return this;
}
Builder setActivity(@NonNull ActivityRecord activity) {
mActivity = requireNonNull(activity);
return this;
}
Builder setTask(@NonNull Task task) {
mTask = requireNonNull(task);
return this;
}
Builder setOpType(@TaskFragmentOperation.OperationType int opType) {
mOpType = opType;
return this;
}
PendingTaskFragmentEvent build() {
return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment,
mErrorCallbackToken, mException, mActivity, mTask, mOpType);
}
}
}
@Nullable
private PendingTaskFragmentEvent getLastPendingLifecycleEvent(@NonNull TaskFragment tf) {
final ITaskFragmentOrganizer organizer = tf.getTaskFragmentOrganizer();
final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
.get(organizer.asBinder());
for (int i = events.size() - 1; i >= 0; i--) {
final PendingTaskFragmentEvent event = events.get(i);
if (tf == event.mTaskFragment && event.isLifecycleEvent()) {
return event;
}
}
return null;
}
@Nullable
private PendingTaskFragmentEvent getPendingTaskFragmentEvent(@NonNull TaskFragment taskFragment,
int type) {
final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer();
final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
.get(organizer.asBinder());
for (int i = events.size() - 1; i >= 0; i--) {
final PendingTaskFragmentEvent event = events.get(i);
if (taskFragment == event.mTaskFragment && type == event.mEventType) {
return event;
}
}
return null;
}
void dispatchPendingEvents() {
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|| mPendingTaskFragmentEvents.isEmpty()) {
return;
}
final int organizerNum = mPendingTaskFragmentEvents.size();
for (int i = 0; i < organizerNum; i++) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(mPendingTaskFragmentEvents.keyAt(i));
dispatchPendingEvents(state, mPendingTaskFragmentEvents.valueAt(i));
}
}
private void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
if (pendingEvents.isEmpty()) {
return;
}
if (shouldDeferPendingEvents(state, pendingEvents)) {
return;
}
mTmpTaskSet.clear();
final int numEvents = pendingEvents.size();
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
for (int i = 0; i < numEvents; i++) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
if (event.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED
|| event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
final Task task = event.mTaskFragment.getTask();
if (mTmpTaskSet.add(task)) {
// Make sure the organizer know about the Task config.
transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, state.mOrganizer)
.setTask(task)
.build()));
}
}
transaction.addChange(prepareChange(event));
}
mTmpTaskSet.clear();
state.dispatchTransaction(transaction);
pendingEvents.clear();
}
/**
* Whether or not to defer sending the events to the organizer to avoid waking the app process
* when it is in background. We want to either send all events or none to avoid inconsistency.
*/
private boolean shouldDeferPendingEvents(@NonNull TaskFragmentOrganizerState state,
@NonNull List<PendingTaskFragmentEvent> pendingEvents) {
final ArrayList<Task> visibleTasks = new ArrayList<>();
final ArrayList<Task> invisibleTasks = new ArrayList<>();
for (int i = 0, n = pendingEvents.size(); i < n; i++) {
final PendingTaskFragmentEvent event = pendingEvents.get(i);
if (event.mEventType != PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED
&& event.mEventType != PendingTaskFragmentEvent.EVENT_INFO_CHANGED
&& event.mEventType != PendingTaskFragmentEvent.EVENT_APPEARED) {
// Send events for any other types.
return false;
}
// Check if we should send the event given the Task visibility and events.
final Task task;
if (event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
task = event.mTask;
} else {
task = event.mTaskFragment.getTask();
}
if (task.lastActiveTime > event.mDeferTime
&& isTaskVisible(task, visibleTasks, invisibleTasks)) {
// Send events when the app has at least one visible Task.
return false;
} else if (shouldSendEventWhenTaskInvisible(task, state, event)) {
// Sent events even if the Task is invisible.
return false;
}
// Defer sending events to the organizer until the host task is active (visible) again.
event.mDeferTime = task.lastActiveTime;
}
// Defer for invisible Task.
return true;
}
private static boolean isTaskVisible(@NonNull Task task,
@NonNull ArrayList<Task> knownVisibleTasks,
@NonNull ArrayList<Task> knownInvisibleTasks) {
if (knownVisibleTasks.contains(task)) {
return true;
}
if (knownInvisibleTasks.contains(task)) {
return false;
}
if (task.shouldBeVisible(null /* starting */)) {
knownVisibleTasks.add(task);
return true;
} else {
knownInvisibleTasks.add(task);
return false;
}
}
private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
@NonNull TaskFragmentOrganizerState state,
@NonNull PendingTaskFragmentEvent event) {
final TaskFragmentParentInfo lastParentInfo = state.mLastSentTaskFragmentParentInfos
.get(task.mTaskId);
if (lastParentInfo == null || lastParentInfo.isVisible()) {
// When the Task was visible, or when there was no Task info changed sent (in which case
// the organizer will consider it as visible by default), always send the event to
// update the Task visibility.
return true;
}
if (event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED) {
// Send info changed if the TaskFragment is becoming empty/non-empty so the
// organizer can choose whether or not to remove the TaskFragment.
final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos
.get(event.mTaskFragment);
final boolean isEmpty = event.mTaskFragment.getNonFinishingActivityCount() == 0;
return lastInfo == null || lastInfo.isEmpty() != isEmpty;
}
return false;
}
void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
if (event == null) {
return;
}
final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer();
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
// Make sure the organizer know about the Task config.
transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
.setTask(taskFragment.getTask())
.build()));
transaction.addChange(prepareChange(event));
state.dispatchTransaction(transaction);
mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event);
}
@Nullable
private TaskFragmentTransaction.Change prepareChange(
@NonNull PendingTaskFragmentEvent event) {
final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg;
final TaskFragment taskFragment = event.mTaskFragment;
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder());
if (state == null) {
return null;
}
switch (event.mEventType) {
case PendingTaskFragmentEvent.EVENT_APPEARED:
return state.prepareTaskFragmentAppeared(taskFragment);
case PendingTaskFragmentEvent.EVENT_VANISHED:
return state.prepareTaskFragmentVanished(taskFragment);
case PendingTaskFragmentEvent.EVENT_INFO_CHANGED:
return state.prepareTaskFragmentInfoChanged(taskFragment);
case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED:
return state.prepareTaskFragmentParentInfoChanged(event.mTask);
case PendingTaskFragmentEvent.EVENT_ERROR:
return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
event.mOpType, event.mException);
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
return state.prepareActivityReparentedToTask(event.mActivity);
default:
throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
}
// TODO(b/204399167): change to push the embedded state to the client side
@Override
public boolean isActivityEmbedded(IBinder activityToken) {
synchronized (mGlobalLock) {
final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
if (activity == null) {
return false;
}
final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
}
}
/**
* Trims the given Intent to only those that are needed to for embedding rules. This helps to
* make it safer for cross-uid embedding even if we only send the Intent for trusted embedding.
*/
private static Intent trimIntent(@NonNull Intent intent) {
return new Intent()
.setComponent(intent.getComponent())
.setPackage(intent.getPackage())
.setAction(intent.getAction());
}
}