blob: 4c77f6a7e00d5cdbc9e5118ff3b87db45882104f [file] [log] [blame]
/*
* Copyright (C) 2020 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.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.window.RemoteTransition;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Provider;
/**
* Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}.
*
* @see StageCoordinator
*/
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
public class SplitScreenController implements DragAndDropPolicy.Starter,
RemoteCallable<SplitScreenController> {
private static final String TAG = SplitScreenController.class.getSimpleName();
static final int EXIT_REASON_UNKNOWN = 0;
static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1;
static final int EXIT_REASON_APP_FINISHED = 2;
static final int EXIT_REASON_DEVICE_FOLDED = 3;
static final int EXIT_REASON_DRAG_DIVIDER = 4;
static final int EXIT_REASON_RETURN_HOME = 5;
static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
static final int EXIT_REASON_SCREEN_LOCKED = 7;
static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
EXIT_REASON_APP_FINISHED,
EXIT_REASON_DEVICE_FOLDED,
EXIT_REASON_DRAG_DIVIDER,
EXIT_REASON_RETURN_HOME,
EXIT_REASON_ROOT_TASK_VANISHED,
EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
}
public SplitScreen asSplitScreen() {
return mImpl;
}
@Override
public Context getContext() {
return mContext;
}
@Override
public ShellExecutor getRemoteCallExecutor() {
return mMainExecutor;
}
public void onOrganizerRegistered() {
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);
}
}
public boolean isSplitScreenVisible() {
return mStageCoordinator.isSplitScreenVisible();
}
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (isSplitScreenVisible()) {
int taskId = mStageCoordinator.getTaskId(splitPosition);
return mTaskOrganizer.getRunningTaskInfo(taskId);
}
return null;
}
public boolean isTaskInSplitScreen(int taskId) {
return isSplitScreenVisible()
&& mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
}
public @SplitPosition int getSplitPosition(int taskId) {
return mStageCoordinator.getSplitPosition(taskId);
}
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
new WindowContainerTransaction());
}
private boolean moveToStage(int taskId, @StageType int stageType,
@SplitPosition int stagePosition, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
}
return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
}
public boolean removeFromSideStage(int taskId) {
return mStageCoordinator.removeFromSideStage(taskId);
}
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
public void setSideStageVisibility(boolean visible) {
mStageCoordinator.setSideStageVisibility(visible);
}
public void enterSplitScreen(int taskId, boolean leftOrTop) {
enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
final int stagePosition =
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
moveToStage(taskId, stageType, stagePosition, wct);
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
}
public void onKeyguardOccludedChanged(boolean occluded) {
mStageCoordinator.onKeyguardOccludedChanged(occluded);
}
public void onKeyguardVisibilityChanged(boolean showing) {
mStageCoordinator.onKeyguardVisibilityChanged(showing);
}
public void onFinishedWakingUp() {
mStageCoordinator.onFinishedWakingUp();
}
public void onFinishedGoingToSleep() {
mStageCoordinator.onFinishedGoingToSleep();
}
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
}
public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
}
public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
mStageCoordinator.registerSplitScreenListener(listener);
}
public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
mStageCoordinator.unregisterSplitScreenListener(listener);
}
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
try {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
final int result =
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
mSyncQueue.queue(evictWct);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
}
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
try {
LauncherApps launcherApps =
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
options, user);
mSyncQueue.queue(evictWct);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
}
public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
null /* remote */);
}
private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
if (apps != null) {
for (int i = 0; i < apps.length; ++i) {
if (apps[i].mode == MODE_OPENING) {
t.show(apps[i].leash);
}
}
}
t.apply();
if (finishedCallback != null) {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.e(TAG, "Error finishing legacy transition: ", e);
}
}
mSyncQueue.queue(evictWct);
}
};
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
if (apps.length < 2) return null;
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
if (mSplitTasksContainerLayer != null) {
// Remove the previous layer before recreating
transaction.remove(mSplitTasksContainerLayer);
}
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName("RecentsAnimationSplitTasks")
.setHidden(false)
.setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
mSplitTasksContainerLayer = builder.build();
// Ensure that we order these in the parent in the right z-order as their previous order
Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
int layer = 1;
for (RemoteAnimationTarget appTarget : apps) {
transaction.reparent(appTarget.leash, mSplitTasksContainerLayer);
transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
appTarget.screenSpaceBounds.top);
transaction.setLayer(appTarget.leash, layer++);
}
transaction.apply();
transaction.close();
return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
}
/**
* Sets drag info to be logged when splitscreen is entered.
*/
public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
}
/**
* Return the {@param exitReason} as a string.
*/
public static String exitReasonToString(int exitReason) {
switch (exitReason) {
case EXIT_REASON_UNKNOWN:
return "UNKNOWN_EXIT";
case EXIT_REASON_DRAG_DIVIDER:
return "DRAG_DIVIDER";
case EXIT_REASON_RETURN_HOME:
return "RETURN_HOME";
case EXIT_REASON_SCREEN_LOCKED:
return "SCREEN_LOCKED";
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
return "SCREEN_LOCKED_SHOW_ON_TOP";
case EXIT_REASON_DEVICE_FOLDED:
return "DEVICE_FOLDED";
case EXIT_REASON_ROOT_TASK_VANISHED:
return "ROOT_TASK_VANISHED";
case EXIT_REASON_APP_FINISHED:
return "APP_FINISHED";
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
return "CHILD_TASK_ENTER_PIP";
default:
return "unknown reason, reason int = " + exitReason;
}
}
public void dump(@NonNull PrintWriter pw, String prefix) {
pw.println(prefix + TAG);
if (mStageCoordinator != null) {
mStageCoordinator.dump(pw, prefix);
}
}
/**
* The interface for calls from outside the Shell, within the host process.
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
public void onStagePositionChanged(int stage, int position) {
for (int i = 0; i < mExecutors.size(); i++) {
final int index = i;
mExecutors.valueAt(index).execute(() -> {
mExecutors.keyAt(index).onStagePositionChanged(stage, position);
});
}
}
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
for (int i = 0; i < mExecutors.size(); i++) {
final int index = i;
mExecutors.valueAt(index).execute(() -> {
mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
});
}
}
@Override
public void onSplitVisibilityChanged(boolean visible) {
for (int i = 0; i < mExecutors.size(); i++) {
final int index = i;
mExecutors.valueAt(index).execute(() -> {
mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
});
}
}
};
@Override
public ISplitScreen createExternalInterface() {
if (mISplitScreen != null) {
mISplitScreen.invalidate();
}
mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
return mISplitScreen;
}
@Override
public void onKeyguardOccludedChanged(boolean occluded) {
mMainExecutor.execute(() -> {
SplitScreenController.this.onKeyguardOccludedChanged(occluded);
});
}
@Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
mMainExecutor.execute(() -> {
if (mExecutors.size() == 0) {
SplitScreenController.this.registerSplitScreenListener(mListener);
}
mExecutors.put(listener, executor);
});
executor.execute(() -> {
mStageCoordinator.sendStatusToListener(listener);
});
}
@Override
public void unregisterSplitScreenListener(SplitScreenListener listener) {
mMainExecutor.execute(() -> {
mExecutors.remove(listener);
if (mExecutors.size() == 0) {
SplitScreenController.this.unregisterSplitScreenListener(mListener);
}
});
}
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
mMainExecutor.execute(() -> {
SplitScreenController.this.onKeyguardVisibilityChanged(showing);
});
}
@Override
public void onFinishedWakingUp() {
mMainExecutor.execute(() -> {
SplitScreenController.this.onFinishedWakingUp();
});
}
@Override
public void onFinishedGoingToSleep() {
mMainExecutor.execute(() -> {
SplitScreenController.this.onFinishedGoingToSleep();
});
}
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class ISplitScreenImpl extends ISplitScreen.Stub {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
public void onStagePositionChanged(int stage, int position) {
mListener.call(l -> l.onStagePositionChanged(stage, position));
}
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
}
};
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
}
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
void invalidate() {
mController = null;
}
@Override
public void registerSplitScreenListener(ISplitScreenListener listener) {
executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
(controller) -> mListener.register(listener));
}
@Override
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
(controller) -> mListener.unregister());
}
@Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> {
controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
});
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
(controller) -> {
controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
});
}
@Override
public void setSideStageVisibility(boolean visible) {
executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
(controller) -> {
controller.setSideStageVisibility(visible);
});
}
@Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
(controller) -> {
controller.removeFromSideStage(taskId);
});
}
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
(controller) -> {
controller.startTask(taskId, position, options);
});
}
@Override
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
splitRatio, adapter));
}
@Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio,
@Nullable RemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition));
}
@Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
(controller) -> {
controller.startShortcut(packageName, shortcutId, position, options, user);
});
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
@Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
(controller) -> {
controller.startIntent(intent, fillInIntent, position, options);
});
}
@Override
public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
RemoteAnimationTarget[] apps) {
final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
(controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
true /* blocking */);
return out[0];
}
}
}