blob: 19b1374ae767d7b963bb00d9c4ab1f96f559f8c5 [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 android.window;
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 android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Interface for WindowManager to delegate control of {@code TaskFragment}.
* @hide
*/
@TestApi
public class TaskFragmentOrganizer extends WindowOrganizer {
/**
* Key to the {@link Throwable} in {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/
public static final String KEY_ERROR_CALLBACK_THROWABLE = "fragment_throwable";
/**
* Key to the {@link TaskFragmentInfo} in
* {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/
public static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
/**
* Key to the {@link WindowContainerTransaction.HierarchyOp} in
* {@link TaskFragmentTransaction.Change#getErrorBundle()}.
* @hide
*/
public static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
/**
* Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
* that can be passed to {@link ITaskFragmentOrganizer#onTaskFragmentError}.
* @hide
*/
public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
@Nullable TaskFragmentInfo info, int opType) {
final Bundle errorBundle = new Bundle();
errorBundle.putSerializable(KEY_ERROR_CALLBACK_THROWABLE, exception);
if (info != null) {
errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
}
errorBundle.putInt(KEY_ERROR_CALLBACK_OP_TYPE, opType);
return errorBundle;
}
/**
* Callbacks from WM Core are posted on this executor.
*/
private final Executor mExecutor;
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
/** Map from Task id to client tokens of TaskFragments in the Task. */
private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>();
/** Map from Task id to Task configuration. */
private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>();
public TaskFragmentOrganizer(@NonNull Executor executor) {
mExecutor = executor;
}
/**
* Gets the executor to run callbacks on.
*/
@NonNull
public Executor getExecutor() {
return mExecutor;
}
/**
* Registers a TaskFragmentOrganizer to manage TaskFragments.
*/
@CallSuper
public void registerOrganizer() {
try {
getController().registerOrganizer(mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unregisters a previously registered TaskFragmentOrganizer.
*/
@CallSuper
public void unregisterOrganizer() {
try {
getController().unregisterOrganizer(mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized
* TaskFragments in the given Task.
*
* @param taskId overrides if the transition only contains windows belonging to this Task.
* @hide
*/
@CallSuper
public void registerRemoteAnimations(int taskId,
@NonNull RemoteAnimationDefinition definition) {
try {
getController().registerRemoteAnimations(mInterface, taskId, definition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unregisters remote animations per transition type for the organizer.
* @hide
*/
@CallSuper
public void unregisterRemoteAnimations(int taskId) {
try {
getController().unregisterRemoteAnimations(mInterface, taskId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*
* @param transactionToken {@link TaskFragmentTransaction#getTransactionToken()} from
* {@link #onTransactionReady(TaskFragmentTransaction)}
* @param wct {@link WindowContainerTransaction} that the server should apply for
* update of the transaction.
* @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission
* requirement.
* @hide
*/
public void onTransactionHandled(@NonNull IBinder transactionToken,
@NonNull WindowContainerTransaction wct) {
wct.setTaskFragmentOrganizer(mInterface);
try {
getController().onTransactionHandled(mInterface, transactionToken, wct);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Called when a TaskFragment is created and organized by this organizer.
*
* @param taskFragmentInfo Info of the TaskFragment that is created.
* @deprecated Use {@link #onTaskFragmentAppeared(WindowContainerTransaction, TaskFragmentInfo)}
* instead.
*/
@Deprecated
public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
* Called when a TaskFragment is created and organized by this organizer.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskFragmentInfo Info of the TaskFragment that is created.
*/
public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
onTaskFragmentAppeared(taskFragmentInfo);
}
/**
* Called when the status of an organized TaskFragment is changed.
*
* @param taskFragmentInfo Info of the TaskFragment that is changed.
* @deprecated Use {@link #onTaskFragmentInfoChanged(WindowContainerTransaction,
* TaskFragmentInfo)} instead.
*/
@Deprecated
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
* Called when the status of an organized TaskFragment is changed.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskFragmentInfo Info of the TaskFragment that is changed.
*/
public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
onTaskFragmentInfoChanged(taskFragmentInfo);
}
/**
* Called when an organized TaskFragment is removed.
*
* @param taskFragmentInfo Info of the TaskFragment that is removed.
* @deprecated Use {@link #onTaskFragmentVanished(WindowContainerTransaction,
* TaskFragmentInfo)} instead.
*/
@Deprecated
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
* Called when an organized TaskFragment is removed.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskFragmentInfo Info of the TaskFragment that is removed.
*/
public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentInfo taskFragmentInfo) {
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
onTaskFragmentVanished(taskFragmentInfo);
}
/**
* Called when the parent leaf Task of organized TaskFragments is changed.
* When the leaf Task is changed, the organizer may want to update the TaskFragments in one
* transaction.
*
* For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
* Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
* bounds.
*
* @param fragmentToken The parent Task this TaskFragment is changed.
* @param parentConfig Config of the parent Task.
* @deprecated Use {@link #onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
* Configuration)} instead.
*/
@Deprecated
public void onTaskFragmentParentInfoChanged(
@NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
/**
* Called when the parent leaf Task of organized TaskFragments is changed.
* When the leaf Task is changed, the organizer may want to update the TaskFragments in one
* transaction.
*
* For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
* Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
* bounds.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskId Id of the parent Task that is changed.
* @param parentConfig Config of the parent Task.
*/
public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId,
@NonNull Configuration parentConfig) {
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
if (tokens == null || tokens.isEmpty()) {
return;
}
for (int i = tokens.size() - 1; i >= 0; i--) {
onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig);
}
}
/**
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param exception exception from the server side.
* @deprecated Use {@link #onTaskFragmentError(WindowContainerTransaction, IBinder,
* TaskFragmentInfo, int, Throwable)} instead.
*/
@Deprecated
public void onTaskFragmentError(
@NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
/**
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
* TaskFragment created.
* @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
* transaction operation.
* @param exception exception from the server side.
*/
public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
// Doing so to keep compatibility. This will be removed in the next release.
onTaskFragmentError(errorCallbackToken, exception);
}
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
* original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
* @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
* need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskId The Task that the activity is reparented to.
* @param activityIntent The intent that the activity is original launched with.
* @param activityToken If the activity belongs to the same process as the organizer, this
* will be the actual activity token; if the activity belongs to a
* different process, the server will generate a temporary token that
* the organizer can use to reparent the activity through
* {@link WindowContainerTransaction} if needed.
*/
public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {}
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
* @hide
*/
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
// TODO(b/240519866): move to SplitController#onTransactionReady to make sure the whole
// transaction is handled in one sync block. Keep the implementation below to keep CTS
// compatibility. Remove in the next release.
final WindowContainerTransaction wct = new WindowContainerTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
switch (change.getType()) {
case TYPE_TASK_FRAGMENT_APPEARED:
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
// release.
if (!mTaskIdToFragmentTokens.contains(taskId)) {
mTaskIdToFragmentTokens.put(taskId, new ArrayList<>());
}
mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken());
onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(),
mTaskIdToConfigurations.get(taskId));
onTaskFragmentAppeared(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_INFO_CHANGED:
onTaskFragmentInfoChanged(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_VANISHED:
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
// release.
if (mTaskIdToFragmentTokens.contains(taskId)) {
final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
tokens.remove(change.getTaskFragmentToken());
if (tokens.isEmpty()) {
mTaskIdToFragmentTokens.remove(taskId);
mTaskIdToConfigurations.remove(taskId);
}
}
onTaskFragmentVanished(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
// TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
// release.
mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration());
onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
break;
case TYPE_TASK_FRAGMENT_ERROR:
final Bundle errorBundle = change.getErrorBundle();
onTaskFragmentError(
wct,
change.getErrorCallbackToken(),
errorBundle.getParcelable(
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
errorBundle.getSerializable(KEY_ERROR_CALLBACK_THROWABLE,
java.lang.Throwable.class));
break;
case TYPE_ACTIVITY_REPARENTED_TO_TASK:
onActivityReparentedToTask(
wct,
change.getTaskId(),
change.getActivityIntent(),
change.getActivityToken());
break;
default:
throw new IllegalArgumentException(
"Unknown TaskFragmentEvent=" + change.getType());
}
}
// Notify the server, and the server should apply the WindowContainerTransaction.
onTransactionHandled(transaction.getTransactionToken(), wct);
}
@Override
public void applyTransaction(@NonNull WindowContainerTransaction t) {
t.setTaskFragmentOrganizer(mInterface);
super.applyTransaction(t);
}
// Suppress the lint because it is not a registration method.
@SuppressWarnings("ExecutorRegistration")
@Override
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
t.setTaskFragmentOrganizer(mInterface);
return super.applySyncTransaction(t, callback);
}
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
mExecutor.execute(() -> TaskFragmentOrganizer.this.onTransactionReady(transaction));
}
};
private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface);
@NonNull
public TaskFragmentOrganizerToken getOrganizerToken() {
return mToken;
}
private ITaskFragmentOrganizerController getController() {
try {
return getWindowOrganizerController().getTaskFragmentOrganizerController();
} catch (RemoteException e) {
return null;
}
}
/**
* Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
* only occupies a portion of Task bounds.
* @hide
*/
public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
try {
return getController().isActivityEmbedded(activityToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}