blob: 392d4c2f772ba502dedd780cc8e867e4dcf564d4 [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.window.TaskFragmentOrganizer.putExceptionInBundle;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
import com.android.internal.protolog.common.ProtoLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
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;
/**
* A Map which manages the relationship between
* {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState}
*/
private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState =
new ArrayMap<>();
/**
* A List which manages the TaskFragment pending event {@link PendingTaskFragmentEvent}
*/
private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents =
new ArrayList<>();
TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
mAtmService = atm;
mGlobalLock = atm.mGlobalLock;
}
/**
* 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;
private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos =
new WeakHashMap<>();
private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs =
new WeakHashMap<>();
private final Map<IBinder, ActivityRecord> mTemporaryActivityTokens =
new WeakHashMap<>();
/**
* Map from Task Id to {@link RemoteAnimationDefinition}.
* @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
* RemoteAnimationDefinition) )
*/
private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
new SparseArray<>();
TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
mOrganizer = organizer;
mOrganizerPid = pid;
mOrganizerUid = uid;
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);
}
}
/**
* @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() {
while (!mOrganizedTaskFragments.isEmpty()) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
taskFragment.removeImmediately();
mOrganizedTaskFragments.remove(taskFragment);
}
mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
}
void onTaskFragmentAppeared(TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
try {
mOrganizer.onTaskFragmentAppeared(info);
mLastSentTaskFragmentInfos.put(tf, info);
tf.mTaskFragmentAppearedSent = true;
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e);
}
onTaskFragmentParentInfoChanged(tf);
}
void onTaskFragmentVanished(TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
try {
mOrganizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e);
}
tf.mTaskFragmentAppearedSent = false;
mLastSentTaskFragmentInfos.remove(tf);
mLastSentTaskFragmentParentConfigs.remove(tf);
}
void onTaskFragmentInfoChanged(TaskFragment tf) {
// Parent config may have changed. The controller will check if there is any important
// config change for the organizer.
onTaskFragmentParentInfoChanged(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;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s",
tf.getName());
try {
mOrganizer.onTaskFragmentInfoChanged(info);
mLastSentTaskFragmentInfos.put(tf, info);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
}
}
void onTaskFragmentParentInfoChanged(TaskFragment tf) {
// Check if the parent info is different from the last reported parent info.
if (tf.getParent() == null || tf.getParent().asTask() == null) {
mLastSentTaskFragmentParentConfigs.remove(tf);
return;
}
final Task parent = tf.getParent().asTask();
final Configuration parentConfig = parent.getConfiguration();
final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)
&& parentConfig.windowConfiguration.getWindowingMode()
== lastParentConfig.windowConfiguration.getWindowingMode()) {
return;
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"TaskFragment parent info changed name=%s parentTaskId=%d",
tf.getName(), parent.mTaskId);
try {
mOrganizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
}
}
void onTaskFragmentError(IBinder errorCallbackToken, Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
final Bundle exceptionBundle = putExceptionInBundle(exception);
try {
mOrganizer.onTaskFragmentError(errorCallbackToken, exceptionBundle);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
}
}
void onActivityReparentToTask(ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
return;
}
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;
}
if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded.");
return;
}
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);
try {
mOrganizer.onActivityReparentToTask(task.mTaskId, activity.intent, activityToken);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onActivityReparentToTask callback", e);
}
}
}
@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;
}
@Override
public void registerOrganizer(ITaskFragmentOrganizer organizer) {
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 (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
throw new IllegalStateException(
"Replacing existing organizer currently unsupported");
}
mTaskFragmentOrganizerState.put(organizer.asBinder(),
new TaskFragmentOrganizerState(organizer, pid, uid));
}
}
@Override
public void unregisterOrganizer(ITaskFragmentOrganizer organizer) {
validateAndGetState(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);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId,
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.mRemoteAnimationDefinitions.contains(taskId)) {
throw new IllegalStateException(
"The organizer has already registered remote animations="
+ organizerState.mRemoteAnimationDefinitions.get(taskId)
+ " for TaskId=" + taskId);
}
definition.setCallingPidUid(pid, uid);
organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
}
}
@Override
public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) {
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.mRemoteAnimationDefinitions.remove(taskId);
}
}
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
* {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
ITaskFragmentOrganizer organizer, int taskId) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
return organizerState != null
? organizerState.mRemoteAnimationDefinitions.get(taskId)
: null;
}
}
int getTaskFragmentOrganizerUid(ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
}
void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
if (!state.addTaskFragment(taskFragment)) {
return;
}
PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_APPEARED);
if (pendingEvent == null) {
pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_APPEARED, organizer)
.setTaskFragment(taskFragment)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
}
}
void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
}
void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer,
TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED);
}
private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer,
TaskFragment taskFragment, int eventType) {
validateAndGetState(organizer);
if (!taskFragment.mTaskFragmentAppearedSent) {
// Skip if TaskFragment still not appeared.
return;
}
PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment);
if (pendingEvent == null) {
pendingEvent = new PendingTaskFragmentEvent.Builder(eventType, organizer)
.setTaskFragment(taskFragment)
.build();
} else {
if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) {
// Skipped the info changed event if vanished event is pending.
return;
}
// Remove and add for re-ordering.
mPendingTaskFragmentEvents.remove(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;
}
mPendingTaskFragmentEvents.add(pendingEvent);
}
void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
if (taskFragment == entry.mTaskFragment) {
mPendingTaskFragmentEvents.remove(i);
if (entry.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED) {
// If taskFragment appeared callback is pending, ignore the vanished request.
return;
}
}
}
if (!taskFragment.mTaskFragmentAppearedSent) {
return;
}
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_VANISHED, organizer)
.setTaskFragment(taskFragment)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
state.removeTaskFragment(taskFragment);
}
void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
.setException(exception)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
// Make sure the error event will be dispatched if there are no other changes.
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
void onActivityReparentToTask(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 (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
return;
}
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer)
.setActivity(activity)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
}
private void removeOrganizer(ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
// remove all of the children of the organized TaskFragment
state.dispose();
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.
*/
private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (state == null) {
throw new IllegalArgumentException(
"TaskFragmentOrganizer has not been registered. Organizer=" + organizer);
}
return state;
}
/**
* 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_REPARENT_TO_TASK = 5;
@IntDef(prefix = "EVENT_", value = {
EVENT_APPEARED,
EVENT_VANISHED,
EVENT_INFO_CHANGED,
EVENT_PARENT_INFO_CHANGED,
EVENT_ERROR,
EVENT_ACTIVITY_REPARENT_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;
// 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;
private PendingTaskFragmentEvent(@EventType int eventType,
ITaskFragmentOrganizer taskFragmentOrg,
@Nullable TaskFragment taskFragment,
@Nullable IBinder errorCallbackToken,
@Nullable Throwable exception,
@Nullable ActivityRecord activity) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
mTaskFragment = taskFragment;
mErrorCallbackToken = errorCallbackToken;
mException = exception;
mActivity = activity;
}
/**
* @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;
Builder(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
}
Builder setTaskFragment(@Nullable TaskFragment taskFragment) {
mTaskFragment = taskFragment;
return this;
}
Builder setErrorCallbackToken(@Nullable IBinder errorCallbackToken) {
mErrorCallbackToken = errorCallbackToken;
return this;
}
Builder setException(@Nullable Throwable exception) {
mException = exception;
return this;
}
Builder setActivity(@Nullable ActivityRecord activity) {
mActivity = activity;
return this;
}
PendingTaskFragmentEvent build() {
return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment,
mErrorCallbackToken, mException, mActivity);
}
}
}
@Nullable
private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) {
return entry;
}
}
return null;
}
@Nullable
private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment,
int type) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
if (taskFragment == entry.mTaskFragment && type == entry.mEventType) {
return entry;
}
}
return null;
}
private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
// Send an info changed callback if this event is for the last activities to finish in a
// TaskFragment so that the {@link TaskFragmentOrganizer} can delete this TaskFragment.
return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
&& lastInfo != null && lastInfo.hasRunningActivity() && info.isEmpty();
}
void dispatchPendingEvents() {
if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
|| mPendingTaskFragmentEvents.isEmpty()) {
return;
}
final ArrayList<Task> visibleTasks = new ArrayList<>();
final ArrayList<Task> invisibleTasks = new ArrayList<>();
final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) {
final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
if (task != null && (task.lastActiveTime <= event.mDeferTime
|| !(isTaskVisible(task, visibleTasks, invisibleTasks)
|| shouldSendEventWhenTaskInvisible(event)))) {
// Defer sending events to the TaskFragment until the host task is active again.
event.mDeferTime = task.lastActiveTime;
continue;
}
candidateEvents.add(event);
}
final int numEvents = candidateEvents.size();
for (int i = 0; i < numEvents; i++) {
dispatchEvent(candidateEvents.get(i));
}
if (numEvents > 0) {
mPendingTaskFragmentEvents.removeAll(candidateEvents);
}
}
private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks,
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;
}
}
void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) {
PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
if (event == null) {
return;
}
dispatchEvent(event);
mPendingTaskFragmentEvents.remove(event);
}
private void dispatchEvent(PendingTaskFragmentEvent event) {
final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg;
final TaskFragment taskFragment = event.mTaskFragment;
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder());
if (state == null) {
return;
}
switch (event.mEventType) {
case PendingTaskFragmentEvent.EVENT_APPEARED:
state.onTaskFragmentAppeared(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_VANISHED:
state.onTaskFragmentVanished(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_INFO_CHANGED:
state.onTaskFragmentInfoChanged(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED:
state.onTaskFragmentParentInfoChanged(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_ERROR:
state.onTaskFragmentError(event.mErrorCallbackToken, event.mException);
break;
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
state.onActivityReparentToTask(event.mActivity);
}
}
// 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();
if (taskFragment == null) {
return false;
}
final Task parentTask = taskFragment.getTask();
if (parentTask != null) {
final Rect taskBounds = parentTask.getBounds();
final Rect taskFragBounds = taskFragment.getBounds();
return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
}
return false;
}
}
}