Merge "Support split from fullscreen with shortcuts" into tm-qpr-dev
This commit is contained in:
@@ -70,6 +70,7 @@ import android.view.View;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.window.SplashScreen;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.app.viewcapture.ViewCapture;
|
||||
@@ -135,6 +136,7 @@ import com.android.quickstep.util.QuickstepOnboardingPrefs;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -179,6 +181,8 @@ public class QuickstepLauncher extends Launcher {
|
||||
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
|
||||
private @Nullable RotationChangeProvider mRotationChangeProvider;
|
||||
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
|
||||
|
||||
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
|
||||
/**
|
||||
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
|
||||
* recover. In all other cases this will remain null.
|
||||
@@ -194,11 +198,13 @@ public class QuickstepLauncher extends Launcher {
|
||||
super.setupViews();
|
||||
|
||||
mActionsView = findViewById(R.id.overview_actions_view);
|
||||
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
|
||||
RecentsView overviewPanel = getOverviewPanel();
|
||||
SplitSelectStateController controller =
|
||||
new SplitSelectStateController(this, mHandler, getStateManager(),
|
||||
getDepthController(), getStatsLogManager());
|
||||
overviewPanel.init(mActionsView, controller);
|
||||
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
|
||||
controller);
|
||||
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
|
||||
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
|
||||
|
||||
@@ -321,6 +327,17 @@ public class QuickstepLauncher extends Launcher {
|
||||
super.showAllAppsFromIntent(alreadyOnHome);
|
||||
}
|
||||
|
||||
protected void onItemClicked(View view) {
|
||||
if (!mSplitWithKeyboardShortcutController.handleSecondAppSelectionForSplit(view)) {
|
||||
QuickstepLauncher.super.getItemOnClickListener().onClick(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getItemOnClickListener() {
|
||||
return this::onItemClicked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
|
||||
Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
|
||||
@@ -402,6 +419,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
super.onDestroy();
|
||||
mHotseatPredictionController.destroy();
|
||||
mSplitWithKeyboardShortcutController.onDestroy();
|
||||
if (mViewCapture != null) mViewCapture.close();
|
||||
}
|
||||
|
||||
@@ -832,6 +850,12 @@ public class QuickstepLauncher extends Launcher {
|
||||
return activityOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
@BinderThread
|
||||
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
|
||||
mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new launch cookie for the activity launch if supported.
|
||||
*
|
||||
|
||||
@@ -43,7 +43,6 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
@@ -399,9 +398,8 @@ public final class TaskViewUtils {
|
||||
*/
|
||||
public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
|
||||
@NonNull StateManager stateManager, @Nullable DepthController depthController,
|
||||
int initialTaskId, @Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
|
||||
@NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t,
|
||||
@NonNull Runnable finishCallback) {
|
||||
int initialTaskId, int secondTaskId, @NonNull TransitionInfo transitionInfo,
|
||||
SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
|
||||
if (launchingTaskView != null) {
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@@ -491,8 +489,7 @@ public final class TaskViewUtils {
|
||||
* If it is null, then it will simply fade in the starting apps and fade out launcher (for the
|
||||
* case where launcher handles animating starting split tasks from app icon) */
|
||||
public static void composeRecentsSplitLaunchAnimatorLegacy(
|
||||
@Nullable GroupedTaskView launchingTaskView, int initialTaskId,
|
||||
@Nullable PendingIntent initialTaskPendingIntent, int secondTaskId,
|
||||
@Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
|
||||
@NonNull RemoteAnimationTarget[] appTargets,
|
||||
@NonNull RemoteAnimationTarget[] wallpaperTargets,
|
||||
@NonNull RemoteAnimationTarget[] nonAppTargets,
|
||||
|
||||
@@ -294,6 +294,16 @@ public class TouchInteractionService extends Service
|
||||
MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurningOff);
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
|
||||
StatefulActivity activity =
|
||||
mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
|
||||
if (activity != null) {
|
||||
activity.enterStageSplitFromRunningApp(leftOrTop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads the Overview activity.
|
||||
*
|
||||
|
||||
@@ -22,8 +22,10 @@ import static android.app.PendingIntent.FLAG_MUTABLE;
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.PendingIntent;
|
||||
@@ -57,6 +59,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskAnimationManager;
|
||||
import com.android.quickstep.TaskViewUtils;
|
||||
import com.android.quickstep.views.FloatingTaskView;
|
||||
import com.android.quickstep.views.GroupedTaskView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -84,6 +87,8 @@ public class SplitSelectStateController {
|
||||
private ItemInfo mItemInfo;
|
||||
private Intent mInitialTaskIntent;
|
||||
private int mInitialTaskId = INVALID_TASK_ID;
|
||||
private String mInitialTaskPackageName;
|
||||
private Intent mSecondTaskIntent;
|
||||
private int mSecondTaskId = INVALID_TASK_ID;
|
||||
private String mSecondTaskPackageName;
|
||||
private boolean mRecentsAnimationRunning;
|
||||
@@ -95,6 +100,8 @@ public class SplitSelectStateController {
|
||||
/** Represents where split is intended to be invoked from. */
|
||||
private StatsLogManager.EventEnum mSplitEvent;
|
||||
|
||||
private FloatingTaskView mFirstFloatingTaskView;
|
||||
|
||||
public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
|
||||
DepthController depthController, StatsLogManager statsLogManager) {
|
||||
mContext = context;
|
||||
@@ -106,19 +113,36 @@ public class SplitSelectStateController {
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called after first task selected
|
||||
* To be called after first task selected in Overview.
|
||||
*/
|
||||
public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition,
|
||||
public void setInitialTaskSelect(Task task, @StagePosition int stagePosition,
|
||||
StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
|
||||
mInitialTaskId = taskId;
|
||||
mInitialTaskId = task.key.id;
|
||||
mInitialTaskPackageName = task.getTopComponent().getPackageName();
|
||||
setInitialData(stagePosition, splitEvent, itemInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called after first task selected from home or all apps.
|
||||
*/
|
||||
public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
|
||||
@NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent) {
|
||||
mInitialTaskIntent = intent;
|
||||
mUser = itemInfo.user;
|
||||
mItemInfo = itemInfo;
|
||||
mInitialTaskPackageName = intent.getComponent().getPackageName();
|
||||
setInitialData(stagePosition, splitEvent, itemInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called after first task selected from using a split shortcut from the fullscreen
|
||||
* running app.
|
||||
*/
|
||||
public void setInitialTaskSelect(ActivityManager.RunningTaskInfo info,
|
||||
@StagePosition int stagePosition, @NonNull ItemInfo itemInfo,
|
||||
StatsLogManager.EventEnum splitEvent) {
|
||||
mInitialTaskId = info.taskId;
|
||||
mInitialTaskPackageName = info.topActivity.getPackageName();
|
||||
setInitialData(stagePosition, splitEvent, itemInfo);
|
||||
}
|
||||
|
||||
@@ -134,27 +158,11 @@ public class SplitSelectStateController {
|
||||
* to be launched. Call after launcher side animations are complete.
|
||||
*/
|
||||
public void launchSplitTasks(Consumer<Boolean> callback) {
|
||||
final Intent fillInIntent;
|
||||
if (mInitialTaskIntent != null) {
|
||||
fillInIntent = new Intent();
|
||||
if (TextUtils.equals(mInitialTaskIntent.getComponent().getPackageName(),
|
||||
mSecondTaskPackageName)) {
|
||||
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
}
|
||||
} else {
|
||||
fillInIntent = null;
|
||||
}
|
||||
|
||||
final PendingIntent pendingIntent = mInitialTaskIntent == null ? null : (mUser != null
|
||||
? PendingIntent.getActivityAsUser(mContext, 0, mInitialTaskIntent,
|
||||
FLAG_MUTABLE, null /* options */, mUser)
|
||||
: PendingIntent.getActivity(mContext, 0, mInitialTaskIntent, FLAG_MUTABLE));
|
||||
|
||||
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
|
||||
LogUtils.getShellShareableInstanceId();
|
||||
launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
|
||||
callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
|
||||
instanceIds.first);
|
||||
launchTasks(mInitialTaskId, mInitialTaskIntent, mInitialTaskPackageName, mSecondTaskId,
|
||||
mSecondTaskIntent, mSecondTaskPackageName, mStagePosition, callback,
|
||||
false /* freezeTaskList */, DEFAULT_SPLIT_RATIO, instanceIds.first);
|
||||
|
||||
mStatsLogManager.logger()
|
||||
.withItemInfo(mItemInfo)
|
||||
@@ -162,23 +170,25 @@ public class SplitSelectStateController {
|
||||
.log(mSplitEvent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To be called as soon as user selects the second task (even if animations aren't complete)
|
||||
* @param task The second task that will be launched.
|
||||
*/
|
||||
public void setSecondTask(Task task) {
|
||||
mSecondTaskId = task.key.id;
|
||||
if (mInitialTaskIntent != null) {
|
||||
mSecondTaskPackageName = task.getTopComponent().getPackageName();
|
||||
}
|
||||
mSecondTaskPackageName = task.getTopComponent().getPackageName();
|
||||
}
|
||||
|
||||
public void setSecondTask(Intent intent) {
|
||||
mSecondTaskIntent = intent;
|
||||
mSecondTaskPackageName = intent.getComponent().getPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when we want to launch split pairs from an existing GroupedTaskView.
|
||||
*/
|
||||
public void launchTasks(GroupedTaskView groupedTaskView,
|
||||
Consumer<Boolean> callback, boolean freezeTaskList) {
|
||||
public void launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback,
|
||||
boolean freezeTaskList) {
|
||||
mLaunchingTaskView = groupedTaskView;
|
||||
TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
|
||||
groupedTaskView.getTaskIdAttributeContainers();
|
||||
@@ -194,22 +204,23 @@ public class SplitSelectStateController {
|
||||
*/
|
||||
public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
|
||||
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
|
||||
launchTasks(taskId1, null /* taskPendingIntent */, null /* fillInIntent */, taskId2,
|
||||
stagePosition, callback, freezeTaskList, splitRatio, null);
|
||||
launchTasks(taskId1, null /* intent1 */, null /* packageName1 */, taskId2,
|
||||
null /* intent2 */, null /* packageName2 */, stagePosition, callback,
|
||||
freezeTaskList, splitRatio, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when we want to launch split pairs from Overview. Split can be initiated from
|
||||
* either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
|
||||
* fill in intent with a taskId2 are set.
|
||||
* @param taskPendingIntent is null when split is initiated from Overview
|
||||
* @param intent1 is null when split is initiated from Overview
|
||||
* @param stagePosition representing location of task1
|
||||
* @param shellInstanceId loggingId to be used by shell, will be non-null for actions that create
|
||||
* a split instance, null for cases that bring existing instaces to the
|
||||
* foreground (quickswitch, launching previous pairs from overview)
|
||||
*/
|
||||
public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
|
||||
@Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
|
||||
public void launchTasks(int taskId1, @Nullable Intent intent1, String packageName1, int taskId2,
|
||||
@Nullable Intent intent2, String packageName2, @StagePosition int stagePosition,
|
||||
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
|
||||
@Nullable InstanceId shellInstanceId) {
|
||||
TestLogging.recordEvent(
|
||||
@@ -220,57 +231,107 @@ public class SplitSelectStateController {
|
||||
}
|
||||
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
|
||||
final RemoteSplitLaunchTransitionRunner animationRunner =
|
||||
new RemoteSplitLaunchTransitionRunner(taskId1, taskPendingIntent, taskId2,
|
||||
callback);
|
||||
new RemoteSplitLaunchTransitionRunner(taskId1, taskId2, callback);
|
||||
final RemoteTransitionCompat remoteTransition = new RemoteTransitionCompat(
|
||||
animationRunner, MAIN_EXECUTOR,
|
||||
ActivityThread.currentActivityThread().getApplicationThread());
|
||||
if (taskPendingIntent == null) {
|
||||
if (intent1 == null && intent2 == null) {
|
||||
mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
|
||||
null /* options2 */, stagePosition, splitRatio, remoteTransition,
|
||||
shellInstanceId);
|
||||
} else if (intent2 == null) {
|
||||
launchIntentOrShortcut(intent1, packageName2, options1, taskId2, stagePosition,
|
||||
splitRatio, remoteTransition, shellInstanceId);
|
||||
} else if (intent1 == null) {
|
||||
launchIntentOrShortcut(intent2, packageName1, options1, taskId1,
|
||||
getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
|
||||
shellInstanceId);
|
||||
} else {
|
||||
final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
|
||||
taskPendingIntent.getCreatorUserHandle());
|
||||
if (shortcutInfo != null) {
|
||||
mSystemUiProxy.startShortcutAndTask(shortcutInfo,
|
||||
options1.toBundle(), taskId2, null /* options2 */, stagePosition,
|
||||
splitRatio, remoteTransition, shellInstanceId);
|
||||
} else {
|
||||
mSystemUiProxy.startIntentAndTask(taskPendingIntent,
|
||||
fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
|
||||
stagePosition, splitRatio, remoteTransition, shellInstanceId);
|
||||
}
|
||||
// TODO: the case when both split apps are started from an intent.
|
||||
}
|
||||
} else {
|
||||
final RemoteSplitLaunchAnimationRunner animationRunner =
|
||||
new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2,
|
||||
callback);
|
||||
new RemoteSplitLaunchAnimationRunner(taskId1, taskId2, callback);
|
||||
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
|
||||
RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
|
||||
300, 150,
|
||||
ActivityThread.currentActivityThread().getApplicationThread());
|
||||
|
||||
if (taskPendingIntent == null) {
|
||||
if (intent1 == null && intent2 == null) {
|
||||
mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
|
||||
taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
|
||||
shellInstanceId);
|
||||
} else if (intent2 == null) {
|
||||
launchIntentOrShortcutLegacy(intent1, packageName2, options1, taskId2,
|
||||
stagePosition, splitRatio, adapter, shellInstanceId);
|
||||
} else if (intent1 == null) {
|
||||
launchIntentOrShortcutLegacy(intent2, packageName1, options1, taskId1,
|
||||
getOppositeStagePosition(stagePosition), splitRatio, adapter,
|
||||
shellInstanceId);
|
||||
} else {
|
||||
final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
|
||||
taskPendingIntent.getCreatorUserHandle());
|
||||
if (shortcutInfo != null) {
|
||||
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
|
||||
options1.toBundle(), taskId2, null /* options2 */, stagePosition,
|
||||
splitRatio, adapter, shellInstanceId);
|
||||
} else {
|
||||
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
|
||||
fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
|
||||
stagePosition, splitRatio, adapter, shellInstanceId);
|
||||
}
|
||||
// TODO: the case when both split apps are started from an intent.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void launchIntentOrShortcut(Intent intent, String otherTaskPackageName,
|
||||
ActivityOptions options1, int taskId, @StagePosition int stagePosition,
|
||||
float splitRatio, RemoteTransitionCompat remoteTransition,
|
||||
@Nullable InstanceId shellInstanceId) {
|
||||
PendingIntent pendingIntent = getPendingIntent(intent);
|
||||
final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
|
||||
pendingIntent.getCreatorUserHandle());
|
||||
if (shortcutInfo != null) {
|
||||
mSystemUiProxy.startShortcutAndTask(shortcutInfo,
|
||||
options1.toBundle(), taskId, null /* options2 */, stagePosition,
|
||||
splitRatio, remoteTransition, shellInstanceId);
|
||||
} else {
|
||||
mSystemUiProxy.startIntentAndTask(pendingIntent,
|
||||
getFillInIntent(intent, otherTaskPackageName), options1.toBundle(), taskId,
|
||||
null /* options2 */, stagePosition, splitRatio, remoteTransition,
|
||||
shellInstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
private void launchIntentOrShortcutLegacy(Intent intent, String otherTaskPackageName,
|
||||
ActivityOptions options1, int taskId, @StagePosition int stagePosition,
|
||||
float splitRatio, RemoteAnimationAdapter adapter,
|
||||
@Nullable InstanceId shellInstanceId) {
|
||||
PendingIntent pendingIntent = getPendingIntent(intent);
|
||||
final ShortcutInfo shortcutInfo = getShortcutInfo(intent,
|
||||
pendingIntent.getCreatorUserHandle());
|
||||
if (shortcutInfo != null) {
|
||||
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
|
||||
options1.toBundle(), taskId, null /* options2 */, stagePosition,
|
||||
splitRatio, adapter, shellInstanceId);
|
||||
} else {
|
||||
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(pendingIntent,
|
||||
getFillInIntent(intent, otherTaskPackageName), options1.toBundle(), taskId,
|
||||
null /* options2 */, stagePosition, splitRatio, adapter,
|
||||
shellInstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent getPendingIntent(Intent intent) {
|
||||
return intent == null ? null : (mUser != null
|
||||
? PendingIntent.getActivityAsUser(mContext, 0, intent,
|
||||
FLAG_MUTABLE, null /* options */, mUser)
|
||||
: PendingIntent.getActivity(mContext, 0, intent, FLAG_MUTABLE));
|
||||
}
|
||||
|
||||
private Intent getFillInIntent(Intent intent, String otherTaskPackageName) {
|
||||
if (intent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Intent fillInIntent = new Intent();
|
||||
if (TextUtils.equals(intent.getComponent().getPackageName(), otherTaskPackageName)) {
|
||||
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
}
|
||||
return fillInIntent;
|
||||
}
|
||||
|
||||
|
||||
public @StagePosition int getActiveSplitStagePosition() {
|
||||
return mStagePosition;
|
||||
}
|
||||
@@ -280,7 +341,7 @@ public class SplitSelectStateController {
|
||||
}
|
||||
|
||||
public void setRecentsAnimationRunning(boolean running) {
|
||||
this.mRecentsAnimationRunning = running;
|
||||
mRecentsAnimationRunning = running;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -311,14 +372,12 @@ public class SplitSelectStateController {
|
||||
private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner {
|
||||
|
||||
private final int mInitialTaskId;
|
||||
private final PendingIntent mInitialTaskPendingIntent;
|
||||
private final int mSecondTaskId;
|
||||
private final Consumer<Boolean> mSuccessCallback;
|
||||
|
||||
RemoteSplitLaunchTransitionRunner(int initialTaskId, PendingIntent initialTaskPendingIntent,
|
||||
int secondTaskId, Consumer<Boolean> callback) {
|
||||
RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
|
||||
Consumer<Boolean> callback) {
|
||||
mInitialTaskId = initialTaskId;
|
||||
mInitialTaskPendingIntent = initialTaskPendingIntent;
|
||||
mSecondTaskId = secondTaskId;
|
||||
mSuccessCallback = callback;
|
||||
}
|
||||
@@ -327,12 +386,11 @@ public class SplitSelectStateController {
|
||||
public void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
|
||||
TaskViewUtils.composeRecentsSplitLaunchAnimator(mLaunchingTaskView, mStateManager,
|
||||
mDepthController, mInitialTaskId, mInitialTaskPendingIntent, mSecondTaskId,
|
||||
info, t, () -> {
|
||||
finishCallback.run();
|
||||
if (mSuccessCallback != null) {
|
||||
mSuccessCallback.accept(true);
|
||||
}
|
||||
mDepthController, mInitialTaskId, mSecondTaskId, info, t, () -> {
|
||||
finishCallback.run();
|
||||
if (mSuccessCallback != null) {
|
||||
mSuccessCallback.accept(true);
|
||||
}
|
||||
});
|
||||
// After successful launch, call resetState
|
||||
resetState();
|
||||
@@ -346,14 +404,12 @@ public class SplitSelectStateController {
|
||||
private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat {
|
||||
|
||||
private final int mInitialTaskId;
|
||||
private final PendingIntent mInitialTaskPendingIntent;
|
||||
private final int mSecondTaskId;
|
||||
private final Consumer<Boolean> mSuccessCallback;
|
||||
|
||||
RemoteSplitLaunchAnimationRunner(int initialTaskId, PendingIntent initialTaskPendingIntent,
|
||||
int secondTaskId, Consumer<Boolean> successCallback) {
|
||||
RemoteSplitLaunchAnimationRunner(int initialTaskId, int secondTaskId,
|
||||
Consumer<Boolean> successCallback) {
|
||||
mInitialTaskId = initialTaskId;
|
||||
mInitialTaskPendingIntent = initialTaskPendingIntent;
|
||||
mSecondTaskId = secondTaskId;
|
||||
mSuccessCallback = successCallback;
|
||||
}
|
||||
@@ -364,9 +420,8 @@ public class SplitSelectStateController {
|
||||
Runnable finishedCallback) {
|
||||
postAsyncCallback(mHandler,
|
||||
() -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
|
||||
mLaunchingTaskView, mInitialTaskId, mInitialTaskPendingIntent,
|
||||
mSecondTaskId, apps, wallpapers, nonApps, mStateManager,
|
||||
mDepthController, () -> {
|
||||
mLaunchingTaskView, mInitialTaskId, mSecondTaskId, apps, wallpapers,
|
||||
nonApps, mStateManager, mDepthController, () -> {
|
||||
finishedCallback.run();
|
||||
if (mSuccessCallback != null) {
|
||||
mSuccessCallback.accept(true);
|
||||
@@ -394,7 +449,10 @@ public class SplitSelectStateController {
|
||||
public void resetState() {
|
||||
mInitialTaskId = INVALID_TASK_ID;
|
||||
mInitialTaskIntent = null;
|
||||
mInitialTaskPackageName = null;
|
||||
mSecondTaskId = INVALID_TASK_ID;
|
||||
mSecondTaskIntent = null;
|
||||
mSecondTaskPackageName = null;
|
||||
mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
|
||||
mRecentsAnimationRunning = false;
|
||||
mLaunchingTaskView = null;
|
||||
@@ -407,7 +465,7 @@ public class SplitSelectStateController {
|
||||
* chosen
|
||||
*/
|
||||
public boolean isSplitSelectActive() {
|
||||
return isInitialTaskIntentSet() && mSecondTaskId == INVALID_TASK_ID;
|
||||
return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,7 +473,7 @@ public class SplitSelectStateController {
|
||||
* be launched
|
||||
*/
|
||||
public boolean isBothSplitAppsConfirmed() {
|
||||
return isInitialTaskIntentSet() && mSecondTaskId != INVALID_TASK_ID;
|
||||
return isInitialTaskIntentSet() && isSecondTaskIntentSet();
|
||||
}
|
||||
|
||||
private boolean isInitialTaskIntentSet() {
|
||||
@@ -425,4 +483,16 @@ public class SplitSelectStateController {
|
||||
public int getInitialTaskId() {
|
||||
return mInitialTaskId;
|
||||
}
|
||||
|
||||
private boolean isSecondTaskIntentSet() {
|
||||
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
|
||||
}
|
||||
|
||||
public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
|
||||
mFirstFloatingTaskView = floatingTaskView;
|
||||
}
|
||||
|
||||
public FloatingTaskView getFirstFloatingTaskView() {
|
||||
return mFirstFloatingTaskView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.quickstep.util;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
import com.android.quickstep.OverviewComponentObserver;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.RecentsAnimationController;
|
||||
import com.android.quickstep.RecentsAnimationDeviceState;
|
||||
import com.android.quickstep.RecentsAnimationTargets;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.views.FloatingTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
/** Transitions app from fullscreen to stage split when triggered from keyboard shortcuts. */
|
||||
public class SplitWithKeyboardShortcutController {
|
||||
|
||||
private final QuickstepLauncher mLauncher;
|
||||
private final SplitSelectStateController mController;
|
||||
private final OverviewComponentObserver mOverviewComponentObserver;
|
||||
|
||||
private final int mSplitPlaceholderSize;
|
||||
private final int mSplitPlaceholderInset;
|
||||
|
||||
public SplitWithKeyboardShortcutController(QuickstepLauncher launcher,
|
||||
SplitSelectStateController controller) {
|
||||
mLauncher = launcher;
|
||||
mController = controller;
|
||||
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
|
||||
launcher.getApplicationContext());
|
||||
mOverviewComponentObserver = new OverviewComponentObserver(launcher.getApplicationContext(),
|
||||
deviceState);
|
||||
|
||||
mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_placeholder_size);
|
||||
mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_placeholder_inset);
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
public void enterStageSplit(boolean leftOrTop) {
|
||||
if (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()) {
|
||||
return;
|
||||
}
|
||||
RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
|
||||
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
|
||||
false /* allowMinimizeSplitScreen */);
|
||||
SplitWithKeyboardShortcutRecentsAnimationListener listener =
|
||||
new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop);
|
||||
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
callbacks.addListener(listener);
|
||||
UI_HELPER_EXECUTOR.execute(
|
||||
// Transition from fullscreen app to enter stage split in launcher with
|
||||
// recents animation.
|
||||
() -> ActivityManagerWrapper.getInstance().startRecentsActivity(
|
||||
mOverviewComponentObserver.getOverviewIntent(),
|
||||
SystemClock.uptimeMillis(), callbacks, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles second app selection from stage split. If the item can't be opened in split or
|
||||
* it's not in stage split state, we pass it onto Launcher's default item click handler.
|
||||
*/
|
||||
public boolean handleSecondAppSelectionForSplit(View view) {
|
||||
if (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
|
||||
|| !mController.isSplitSelectActive()) {
|
||||
return false;
|
||||
}
|
||||
Object tag = view.getTag();
|
||||
Intent intent;
|
||||
if (tag instanceof WorkspaceItemInfo) {
|
||||
final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) tag;
|
||||
intent = workspaceItemInfo.intent;
|
||||
} else if (tag instanceof com.android.launcher3.model.data.AppInfo) {
|
||||
final com.android.launcher3.model.data.AppInfo appInfo =
|
||||
(com.android.launcher3.model.data.AppInfo) tag;
|
||||
intent = appInfo.intent;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
mController.setSecondTask(intent);
|
||||
mController.launchSplitTasks(aBoolean -> mLauncher.getDragLayer().removeView(
|
||||
mController.getFirstFloatingTaskView()));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mOverviewComponentObserver.onDestroy();
|
||||
}
|
||||
|
||||
private class SplitWithKeyboardShortcutRecentsAnimationListener implements
|
||||
RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
|
||||
private final boolean mLeftOrTop;
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
private SplitWithKeyboardShortcutRecentsAnimationListener(boolean leftOrTop) {
|
||||
mLeftOrTop = leftOrTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationStart(RecentsAnimationController controller,
|
||||
RecentsAnimationTargets targets) {
|
||||
ActivityManager.RunningTaskInfo runningTaskInfo =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
mController.setInitialTaskSelect(runningTaskInfo,
|
||||
mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT,
|
||||
null /* itemInfo */,
|
||||
mLeftOrTop ? LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP
|
||||
: LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM);
|
||||
|
||||
RecentsView recentsView = mLauncher.getOverviewPanel();
|
||||
recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
|
||||
mSplitPlaceholderSize, mSplitPlaceholderInset, mLauncher.getDeviceProfile(),
|
||||
mController.getActiveSplitStagePosition(), mTempRect);
|
||||
|
||||
PendingAnimation anim = new PendingAnimation(
|
||||
SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration());
|
||||
RectF startingTaskRect = new RectF();
|
||||
final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(
|
||||
mLauncher, mLauncher.getDragLayer(),
|
||||
controller.screenshotTask(runningTaskInfo.taskId).thumbnail,
|
||||
null /* icon */, startingTaskRect);
|
||||
RecentsModel.INSTANCE.get(mLauncher.getApplicationContext())
|
||||
.getIconCache()
|
||||
.updateIconInBackground(
|
||||
Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
|
||||
false /* isLocked */),
|
||||
(task) -> {
|
||||
if (task.thumbnail != null) {
|
||||
floatingTaskView.setIcon(task.thumbnail.thumbnail);
|
||||
}
|
||||
});
|
||||
floatingTaskView.setAlpha(1);
|
||||
floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
|
||||
false /* fadeWithThumbnail */, true /* isStagedTask */);
|
||||
mController.setFirstFloatingTaskView(floatingTaskView);
|
||||
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
controller.finish(true /* toRecents */, null /* onFinishComplete */,
|
||||
false /* sendUserLeaveHint */);
|
||||
}
|
||||
});
|
||||
anim.buildAnim().start();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
@@ -74,6 +75,7 @@ public class FloatingTaskView extends FrameLayout {
|
||||
}
|
||||
};
|
||||
|
||||
private int mSplitHolderSize;
|
||||
private FloatingTaskThumbnailView mThumbnailView;
|
||||
private SplitPlaceholderView mSplitPlaceholderView;
|
||||
private RectF mStartingPosition;
|
||||
@@ -97,6 +99,9 @@ public class FloatingTaskView extends FrameLayout {
|
||||
mActivity = BaseActivity.fromContext(context);
|
||||
mIsRtl = Utilities.isRtl(getResources());
|
||||
mFullscreenParams = new FullscreenDrawParams(context);
|
||||
|
||||
mSplitHolderSize = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.split_placeholder_icon_size);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,8 +131,7 @@ public class FloatingTaskView extends FrameLayout {
|
||||
RecentsView recentsView = launcher.getOverviewPanel();
|
||||
mOrientationHandler = recentsView.getPagedOrientationHandler();
|
||||
mStagePosition = recentsView.getSplitSelectController().getActiveSplitStagePosition();
|
||||
mSplitPlaceholderView.setIcon(icon,
|
||||
mContext.getResources().getDimensionPixelSize(R.dimen.split_placeholder_icon_size));
|
||||
mSplitPlaceholderView.setIcon(icon, mSplitHolderSize);
|
||||
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
|
||||
}
|
||||
|
||||
@@ -193,6 +197,10 @@ public class FloatingTaskView extends FrameLayout {
|
||||
mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
|
||||
}
|
||||
|
||||
public void setIcon(Bitmap icon) {
|
||||
mSplitPlaceholderView.setIcon(new BitmapDrawable(icon), mSplitHolderSize);
|
||||
}
|
||||
|
||||
protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
|
||||
mStartingPosition.set(pos);
|
||||
lp.ignoreInsets = true;
|
||||
|
||||
@@ -4184,7 +4184,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
|
||||
StatsLogManager.EventEnum splitEvent) {
|
||||
mSplitHiddenTaskView = taskView;
|
||||
mSplitSelectStateController.setInitialTaskSelect(taskView.getTask().key.id,
|
||||
mSplitSelectStateController.setInitialTaskSelect(taskView.getTask(),
|
||||
stagePosition, splitEvent, taskView.getItemInfo());
|
||||
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
|
||||
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
|
||||
|
||||
@@ -1332,7 +1332,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.app_icon, parent, false);
|
||||
favorite.applyFromWorkspaceItem(info);
|
||||
favorite.setOnClickListener(ItemClickHandler.INSTANCE);
|
||||
favorite.setOnClickListener(getItemOnClickListener());
|
||||
favorite.setOnFocusChangeListener(mFocusHandler);
|
||||
return favorite;
|
||||
}
|
||||
|
||||
@@ -253,6 +253,10 @@ public final class FeatureFlags {
|
||||
"ENABLE_SPLIT_FROM_WORKSPACE", true,
|
||||
"Enable initiating split screen from workspace.");
|
||||
|
||||
public static final BooleanFlag ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS =
|
||||
getDebugFlag("ENABLE_SPLIT_FROM_FULLSCREEN_SHORTCUT", false,
|
||||
"Enable splitting from fullscreen app with keyboard shortcuts");
|
||||
|
||||
public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(
|
||||
"ENABLE_NEW_MIGRATION_LOGIC", true,
|
||||
"Enable the new grid migration logic, keeping pages when src < dest");
|
||||
|
||||
@@ -617,6 +617,11 @@ public class StatsLogManager implements ResourceBasedOverride {
|
||||
@UiEvent(doc = "Number of apps in A-Z list (personal and work profile)")
|
||||
LAUNCHER_ALLAPPS_COUNT(1225),
|
||||
|
||||
@UiEvent(doc = "User has invoked split to right half with a keyboard shortcut.")
|
||||
LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM(1232),
|
||||
|
||||
@UiEvent(doc = "User has invoked split to left half with a keyboard shortcut.")
|
||||
LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP(1233)
|
||||
;
|
||||
|
||||
// ADD MORE
|
||||
|
||||
@@ -231,4 +231,10 @@ public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
|
||||
* etc.)
|
||||
*/
|
||||
protected abstract void onHandleConfigurationChanged();
|
||||
|
||||
/**
|
||||
* Enter staged split directly from the current running app.
|
||||
* @param leftOrTop if the staged split will be positioned left or top.
|
||||
*/
|
||||
public void enterStageSplitFromRunningApp(boolean leftOrTop) { }
|
||||
}
|
||||
|
||||
@@ -182,4 +182,12 @@ public final class SplitConfigurationOptions {
|
||||
? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP
|
||||
: LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM;
|
||||
}
|
||||
|
||||
public static @StagePosition int getOppositeStagePosition(@StagePosition int position) {
|
||||
if (position == STAGE_POSITION_UNDEFINED) {
|
||||
return position;
|
||||
}
|
||||
return position == STAGE_POSITION_TOP_OR_LEFT ? STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
: STAGE_POSITION_TOP_OR_LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user