Snap for 10052121 from db07d2223e to udc-release

Change-Id: I1b09fbe4cf7e283e050409d4efee1d3c3daac056
This commit is contained in:
Android Build Coastguard Worker
2023-05-02 23:27:10 +00:00
13 changed files with 630 additions and 62 deletions
@@ -673,18 +673,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return mIsFullscreen;
}
/**
* Notify system to inset the rounded corner frame based on the task bar insets.
*/
public void updateInsetRoundedCornerFrame(boolean shouldInsetsRoundedCorner) {
if (!mDragLayer.isAttachedToWindow()
|| mWindowLayoutParams.insetsRoundedCornerFrame == shouldInsetsRoundedCorner) {
return;
}
mWindowLayoutParams.insetsRoundedCornerFrame = shouldInsetsRoundedCorner;
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
/**
* Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
*/
@@ -71,7 +71,6 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
windowLayoutParams = context.windowLayoutParams
windowLayoutParams.insetsRoundedCornerFrame = true
onTaskbarWindowHeightOrInsetsChanged()
context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
@@ -86,23 +85,23 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
fun onTaskbarWindowHeightOrInsetsChanged() {
if (context.isGestureNav) {
windowLayoutParams.providedInsets =
arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars())
.setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
.setSource(SOURCE_DISPLAY),
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
.setSource(SOURCE_DISPLAY)
)
arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars())
.setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
.setSource(SOURCE_DISPLAY),
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
.setSource(SOURCE_DISPLAY)
)
} else {
windowLayoutParams.providedInsets =
arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars()),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
)
arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars()),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
)
}
val touchableHeight = controllers.taskbarStashController.touchableHeight
@@ -162,6 +161,11 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
provider.insetsSizeOverrides = insetsSizeOverride
}
}
// We only report tappableElement height for unstashed, persistent taskbar,
// which is also when we draw the rounded corners above taskbar.
windowLayoutParams.insetsRoundedCornerFrame = tappableHeight > 0
context.notifyUpdateLayoutParams()
}
@@ -106,9 +106,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
// height. This way the reported insets are consistent even during transitions out of the app.
// Currently any flag that causes us to stash in an app is included, except for IME or All Apps
@@ -413,13 +410,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return hasAnyFlag(FLAGS_STASHED_IN_APP);
}
/**
* Returns whether the taskbar should be stashed in apps regardless of the IME visibility.
*/
public boolean isStashedInAppIgnoringIme() {
return hasAnyFlag(FLAGS_STASHED_IN_APP_IGNORING_IME);
}
/**
* Returns whether the taskbar should be stashed in the current LauncherState.
*/
@@ -1059,11 +1049,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private void notifyStashChange(boolean visible, boolean stashed) {
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
setUpTaskbarSystemAction(visible);
// If stashing taskbar is caused by IME visibility, we could just skip updating rounded
// corner insets since the rounded corners will be covered by IME during IME is showing and
// taskbar will be restored back to unstashed when IME is hidden.
mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(
visible && !isStashedInAppIgnoringIme());
mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
}
@@ -1317,5 +1317,8 @@ public class QuickstepLauncher extends Launcher {
writer.println("\nQuickstepLauncher:");
writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
recentsView.getPagedViewOrientedState()));
if (recentsView != null) {
recentsView.getSplitSelectController().dump(prefix, writer);
}
}
}
@@ -177,7 +177,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
protected Q mRecentsView;
protected @Nullable Q mRecentsView;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
@@ -1895,7 +1895,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private void invalidateHandlerWithLauncher() {
endLauncherTransitionController();
mRecentsView.onGestureAnimationEnd();
if (mRecentsView != null) {
mRecentsView.onGestureAnimationEnd();
}
resetLauncherListeners();
}
@@ -1922,7 +1924,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private void resetLauncherListeners() {
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
if (mRecentsView != null) {
mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
}
}
private void resetStateForAnimationCancel() {
@@ -1981,8 +1985,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
boolean finishTransitionPosted = false;
final TaskView taskView;
if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK
|| mGestureState.getEndTarget() == ALL_APPS) {
if (mGestureState.getEndTarget() == HOME
|| mGestureState.getEndTarget() == NEW_TASK
|| mGestureState.getEndTarget() == ALL_APPS
|| mRecentsView == null) {
// Capture the screenshot before finishing the transition to home or quickswitching to
// ensure it's taken in the correct orientation, but no need to update the thumbnail.
taskView = null;
@@ -2135,7 +2141,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
protected void startNewTask(Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (!mCanceled) {
TaskView nextTask = mRecentsView.getNextPageTaskView();
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
Task.TaskKey nextTaskKey = nextTask.getTask().key;
int taskId = nextTaskKey.id;
@@ -2237,7 +2243,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
@@ -2296,9 +2303,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
* resume if we finish the controller.
*/
protected int getLastAppearedTaskIndex() {
return mGestureState.getLastAppearedTaskId() != -1
? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
: mRecentsView.getRunningTaskIndex();
return mRecentsView == null
? -1
: mGestureState.getLastAppearedTaskId() != -1
? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
: mRecentsView.getRunningTaskIndex();
}
/**
@@ -149,6 +149,11 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
case TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR:
enableTransientTaskbar(false);
return response;
case TestProtocol.REQUEST_SHELL_DRAG_READY:
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
SystemUiProxy.INSTANCE.get(mContext).isDragAndDropReady());
return response;
}
return super.call(method, arg, extras);
@@ -70,6 +70,7 @@ import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
@@ -128,6 +129,7 @@ public class SystemUiProxy implements ISystemUiProxy {
private IBinder mOriginalTransactionToken = null;
private IOnBackInvokedCallback mBackToLauncherCallback;
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -203,7 +205,7 @@ public class SystemUiProxy implements ISystemUiProxy {
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
IBackAnimation backAnimation, IDesktopMode desktopMode,
IUnfoldAnimation unfoldAnimation) {
IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
unlinkToDeath();
mSystemUiProxy = proxy;
mPip = pip;
@@ -216,6 +218,7 @@ public class SystemUiProxy implements ISystemUiProxy {
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
mUnfoldAnimation = unfoldAnimation;
mDragAndDrop = dragAndDrop;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
setPipAnimationListener(mPipAnimationListener);
@@ -230,7 +233,7 @@ public class SystemUiProxy implements ISystemUiProxy {
}
public void clearProxy() {
setProxy(null, null, null, null, null, null, null, null, null, null, null);
setProxy(null, null, null, null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -1099,6 +1102,11 @@ public class SystemUiProxy implements ISystemUiProxy {
Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
}
}
//
// Recents
//
/**
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
@@ -1131,10 +1139,30 @@ public class SystemUiProxy implements ISystemUiProxy {
try {
mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
mContext.getIApplicationThread(), runner);
return true;
} catch (RemoteException e) {
Log.e(TAG, "Error starting recents via shell", e);
return false;
}
return true;
}
//
// Drag and drop
//
/**
* For testing purposes. Returns `true` only if the shell drop target has shown and
* drawn and is ready to handle drag events and the subsequent drop.
*/
public boolean isDragAndDropReady() {
if (mDragAndDrop == null) {
return false;
}
try {
return mDragAndDrop.isReadyToHandleDrag();
} catch (RemoteException e) {
Log.e(TAG, "Error querying drag state", e);
return false;
}
}
}
@@ -43,6 +43,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
@@ -125,6 +126,7 @@ import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
@@ -185,11 +187,13 @@ public class TouchInteractionService extends Service
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition);
unfoldTransition, dragAndDrop);
TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
preloadOverview(true /* fromInit */);
});
@@ -0,0 +1,364 @@
/*
* Copyright (C) 2023 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 android.annotation.IntDef
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.os.UserHandle
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.launcher3.logging.StatsLogManager.EventEnum
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.shortcuts.ShortcutKey
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition
import com.android.quickstep.util.SplitSelectDataHolder.Companion.SplitLaunchType
import java.io.PrintWriter
/**
* Holds/transforms/signs/seals/delivers information for the transient state of the user
* selecting a first app to start split with and then choosing a second app.
* This class DOES NOT associate itself with drag-and-drop split screen starts because they come
* from the bad part of town.
*
* After setting the correct fields for initial/second.* variables, this converts them into the
* correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
* data back via [getSplitLaunchData].
* [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
* state
*/
class SplitSelectDataHolder(
val context: Context
) {
val TAG = SplitSelectDataHolder::class.simpleName
/**
* Order of the constant indicates the order of which task/app was selected.
* Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
* SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task
*/
companion object {
@IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT,
SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK)
@Retention(AnnotationRetention.SOURCE)
annotation class SplitLaunchType
const val SPLIT_TASK_TASK = 0
const val SPLIT_TASK_PENDINGINTENT = 1
const val SPLIT_TASK_SHORTCUT = 2
const val SPLIT_PENDINGINTENT_TASK = 3
const val SPLIT_SHORTCUT_TASK = 4
const val SPLIT_PENDINGINTENT_PENDINGINTENT = 5
}
@StagePosition
private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
private var initialTaskId: Int = INVALID_TASK_ID
private var secondTaskId: Int = INVALID_TASK_ID
private var initialUser: UserHandle? = null
private var secondUser: UserHandle? = null
private var initialIntent: Intent? = null
private var secondIntent: Intent? = null
private var secondPendingIntent: PendingIntent? = null
private var itemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
private var initialShortcut: ShortcutInfo? = null
private var secondShortcut: ShortcutInfo? = null
private var initialPendingIntent: PendingIntent? = null
/**
* @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
* then @param intent will be used to launch the initial task
* @param intent will be ignored if @param alreadyRunningTask is set
*/
fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int,
itemInfo: ItemInfo?, splitEvent: EventEnum?,
alreadyRunningTask: Int) {
if (alreadyRunningTask != INVALID_TASK_ID) {
initialTaskId = alreadyRunningTask
} else {
initialIntent = intent!!
initialUser = itemInfo!!.user
}
setInitialData(stagePosition, splitEvent, itemInfo)
}
/**
* To be called after first task selected from using a split shortcut from the fullscreen
* running app.
*/
fun setInitialTaskSelect(info: RunningTaskInfo,
@StagePosition stagePosition: Int, itemInfo: ItemInfo?,
splitEvent: EventEnum?) {
initialTaskId = info.taskId
setInitialData(stagePosition, splitEvent, itemInfo)
}
private fun setInitialData(@StagePosition stagePosition: Int,
event: EventEnum?, item: ItemInfo?) {
itemInfo = item
initialStagePosition = stagePosition
splitEvent = event
}
/**
* To be called as soon as user selects the second task (even if animations aren't complete)
* @param taskId The second task that will be launched.
*/
fun setSecondTask(taskId: Int) {
secondTaskId = taskId
}
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
fun setSecondTask(intent: Intent, user: UserHandle) {
secondIntent = intent
secondUser = user
}
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
* Sets [secondUser] from that of the pendingIntent
* @param pendingIntent The second PendingIntent that will be launched.
*/
fun setSecondTask(pendingIntent: PendingIntent) {
secondPendingIntent = pendingIntent
secondUser = pendingIntent.creatorUserHandle!!
}
private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
if (intent?.getPackage() == null) {
return null
}
val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
?: return null
try {
val context: Context = context.createPackageContextAsUser(
intent.getPackage(), 0 /* flags */, user)
return ShortcutInfo.Builder(context, shortcutId).build()
} catch (e: PackageManager.NameNotFoundException) {
Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage())
}
return null
}
/**
* Converts intents to pendingIntents, associating the [user] with the intent if provided
*/
private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? {
if (intent != initialIntent && intent != secondIntent) {
throw IllegalStateException("Invalid intent to convert to PendingIntent")
}
return if (intent == null) {
null
} else if (user != null) {
PendingIntent.getActivityAsUser(context, 0, intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
null /* options */, user)
} else {
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
}
}
/**
* @return [SplitLaunchData] with the necessary fields populated as determined by
* [SplitLaunchData.splitLaunchType]
*/
fun getSplitLaunchData() : SplitLaunchData {
// Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getSplitLaunchType()
if (splitLaunchType == SPLIT_TASK_PENDINGINTENT || splitLaunchType == SPLIT_TASK_SHORTCUT) {
// need to get opposite stage position
initialStagePosition = getOppositeStagePosition(initialStagePosition)
}
return SplitLaunchData(
splitLaunchType,
initialTaskId,
secondTaskId,
initialPendingIntent,
secondPendingIntent,
initialShortcut,
secondShortcut,
itemInfo,
splitEvent,
initialStagePosition)
}
/**
* Converts our [initialIntent] and [secondIntent] into shortcuts and pendingIntents, if
* possible.
*
* Note that both [initialIntent] and [secondIntent] will be nullified on method return
*
* One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
* convert [secondIntent]
*/
private fun convertIntentsToFinalTypes() {
initialShortcut = getShortcutInfo(initialIntent, initialUser)
initialPendingIntent = getPendingIntent(initialIntent, initialUser)
initialIntent = null
// Only one of the two is currently allowed (secondPendingIntent directly set for widgets)
if (secondIntent != null && secondPendingIntent != null) {
throw IllegalStateException("Both secondIntent and secondPendingIntent non-null")
}
// If secondPendingIntent already set, no need to convert. Prioritize using that
if (secondPendingIntent != null) {
secondIntent = null
return
}
secondShortcut = getShortcutInfo(secondIntent, secondUser)
secondPendingIntent = getPendingIntent(secondIntent, secondUser)
secondIntent = null
}
/**
* Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
* Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
*/
@VisibleForTesting
@SplitLaunchType
fun getSplitLaunchType(): Int {
if (initialIntent != null || secondIntent != null) {
throw IllegalStateException("Intents need to be converted")
}
// Prioritize task launches first
if (initialTaskId != INVALID_TASK_ID) {
if (secondTaskId != INVALID_TASK_ID) {
return SPLIT_TASK_TASK
}
if (secondShortcut != null) {
return SPLIT_TASK_SHORTCUT
}
if (secondPendingIntent != null) {
return SPLIT_TASK_PENDINGINTENT
}
}
if (secondTaskId != INVALID_TASK_ID) {
if (initialShortcut != null) {
return SPLIT_SHORTCUT_TASK
}
if (initialPendingIntent != null) {
return SPLIT_PENDINGINTENT_TASK
}
}
// All task+shortcut combinations are handled above, only launch left is with multiple
// intents (and respective shortcut infos, if necessary)
if (initialPendingIntent != null && secondPendingIntent != null) {
return SPLIT_PENDINGINTENT_PENDINGINTENT
}
throw IllegalStateException("Unidentified split launch type")
}
data class SplitLaunchData(
@SplitLaunchType
val splitLaunchType: Int,
var initialTaskId: Int = INVALID_TASK_ID,
var secondTaskId: Int = INVALID_TASK_ID,
var initialPendingIntent: PendingIntent? = null,
var secondPendingIntent: PendingIntent? = null,
var initialShortcut: ShortcutInfo? = null,
var secondShortcut: ShortcutInfo? = null,
var itemInfo: ItemInfo? = null,
var splitEvent: EventEnum? = null,
val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
)
/**
* @return `true` if first task has been selected and waiting for the second task to be
* chosen
*/
fun isSplitSelectActive(): Boolean {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet()
}
/**
* @return `true` if the first and second task have been chosen and split is waiting to
* be launched
*/
fun isBothSplitAppsConfirmed(): Boolean {
return isInitialTaskIntentSet() && isSecondTaskIntentSet()
}
private fun isInitialTaskIntentSet(): Boolean {
return initialTaskId != INVALID_TASK_ID || initialIntent != null
}
fun getInitialTaskId(): Int {
return initialTaskId
}
fun getSecondTaskId(): Int {
return secondTaskId
}
private fun isSecondTaskIntentSet(): Boolean {
return secondTaskId != INVALID_TASK_ID || secondIntent != null
|| secondPendingIntent != null
}
fun resetState() {
initialStagePosition = STAGE_POSITION_UNDEFINED
initialTaskId = INVALID_TASK_ID
secondTaskId = INVALID_TASK_ID
initialUser = null
secondUser = null
initialIntent = null
secondIntent = null
secondPendingIntent = null
itemInfo = null
splitEvent = null
initialShortcut = null
secondShortcut = null
}
fun dump(prefix: String, writer: PrintWriter) {
writer.println("$prefix ${javaClass.simpleName}")
writer.println("$prefix\tinitialStagePosition= $initialStagePosition")
writer.println("$prefix\tinitialTaskId= $initialTaskId")
writer.println("$prefix\tsecondTaskId= $secondTaskId")
writer.println("$prefix\tinitialUser= $initialUser")
writer.println("$prefix\tsecondUser= $secondUser")
writer.println("$prefix\tinitialIntent= $initialIntent")
writer.println("$prefix\tsecondIntent= $secondIntent")
writer.println("$prefix\tsecondPendingIntent= $secondPendingIntent")
writer.println("$prefix\titemInfo= $itemInfo")
writer.println("$prefix\tsplitEvent= $splitEvent")
writer.println("$prefix\tinitialShortcut= $initialShortcut")
writer.println("$prefix\tsecondShortcut= $secondShortcut")
}
}
@@ -24,6 +24,12 @@ 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 static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -34,6 +40,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -51,6 +58,7 @@ import android.window.TransitionInfo;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -71,6 +79,7 @@ import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -85,6 +94,7 @@ public class SplitSelectStateController {
private final RecentsModel mRecentTasksModel;
private final SplitAnimationController mSplitAnimationController;
private final AppPairsController mAppPairsController;
private final SplitSelectDataHolder mSplitSelectDataHolder;
private StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
@@ -137,6 +147,7 @@ public class SplitSelectStateController {
mRecentTasksModel = recentsModel;
mSplitAnimationController = new SplitAnimationController(this);
mAppPairsController = new AppPairsController(context, this);
mSplitSelectDataHolder = new SplitSelectDataHolder(mContext);
}
/**
@@ -155,6 +166,11 @@ public class SplitSelectStateController {
}
setInitialData(stagePosition, splitEvent, itemInfo);
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
alreadyRunningTask);
}
}
/**
@@ -166,6 +182,10 @@ public class SplitSelectStateController {
StatsLogManager.EventEnum splitEvent) {
mInitialTaskId = info.taskId;
setInitialData(stagePosition, splitEvent, itemInfo);
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
}
}
private void setInitialData(@StagePosition int stagePosition,
@@ -243,6 +263,10 @@ public class SplitSelectStateController {
*/
public void setSecondTask(Task task) {
mSecondTaskId = task.key.id;
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setSecondTask(task.key.id);
}
}
/**
@@ -253,6 +277,10 @@ public class SplitSelectStateController {
public void setSecondTask(Intent intent, UserHandle user) {
mSecondTaskIntent = intent;
mSecondUser = user;
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setSecondTask(intent, user);
}
}
/**
@@ -263,6 +291,10 @@ public class SplitSelectStateController {
public void setSecondTask(PendingIntent pendingIntent) {
mSecondPendingIntent = pendingIntent;
mSecondUser = pendingIntent.getCreatorUserHandle();
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setSecondTask(pendingIntent);
}
}
/**
@@ -285,6 +317,12 @@ public class SplitSelectStateController {
*/
public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.setInitialTaskSelect(null /*intent*/,
stagePosition, null /*itemInfo*/, null /*splitEvent*/,
taskId1);
mSplitSelectDataHolder.setSecondTask(taskId2);
}
launchTasks(taskId1, null /* intent1 */, taskId2, null /* intent2 */, stagePosition,
callback, freezeTaskList, splitRatio, null);
}
@@ -305,6 +343,11 @@ public class SplitSelectStateController {
@Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
return;
}
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
@@ -367,6 +410,101 @@ public class SplitSelectStateController {
}
}
private void launchTasksRefactored(Consumer<Boolean> callback, boolean freezeTaskList,
float splitRatio, @Nullable InstanceId shellInstanceId) {
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
}
SplitSelectDataHolder.SplitLaunchData launchData =
mSplitSelectDataHolder.getSplitLaunchData();
int firstTaskId = launchData.getInitialTaskId();
int secondTaskId = launchData.getSecondTaskId();
ShortcutInfo firstShortcut = launchData.getInitialShortcut();
ShortcutInfo secondShortcut = launchData.getSecondShortcut();
PendingIntent firstPI = launchData.getInitialPendingIntent();
PendingIntent secondPI = launchData.getSecondPendingIntent();
int initialStagePosition = launchData.getInitialStagePosition();
Bundle optionsBundle = options1.toBundle();
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
final RemoteSplitLaunchTransitionRunner animationRunner =
new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
ActivityThread.currentActivityThread().getApplicationThread(),
"LaunchSplitPair");
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTask(secondPI, optionsBundle, firstTaskId,
null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTask(firstPI, optionsBundle, secondTaskId,
null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntents(firstPI, firstShortcut, optionsBundle, secondPI,
secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
remoteTransition, shellInstanceId);
}
} else {
final RemoteSplitLaunchAnimationRunner animationRunner =
new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
animationRunner, 300, 150,
ActivityThread.currentActivityThread().getApplicationThread());
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
secondTaskId, null /* options2 */, initialStagePosition,
splitRatio, adapter, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
splitRatio, adapter, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
splitRatio, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstShortcut,
optionsBundle, secondPI, secondShortcut, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
}
}
}
private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
int taskId, @StagePosition int stagePosition, float splitRatio,
RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
@@ -572,6 +710,9 @@ public class SplitSelectStateController {
* To be called if split select was cancelled
*/
public void resetState() {
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
mSplitSelectDataHolder.resetState();
}
mInitialTaskId = INVALID_TASK_ID;
mInitialTaskIntent = null;
mSecondTaskId = INVALID_TASK_ID;
@@ -593,7 +734,11 @@ public class SplitSelectStateController {
* chosen
*/
public boolean isSplitSelectActive() {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
return mSplitSelectDataHolder.isSplitSelectActive();
} else {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
}
}
/**
@@ -601,7 +746,11 @@ public class SplitSelectStateController {
* be launched
*/
public boolean isBothSplitAppsConfirmed() {
return isInitialTaskIntentSet() && isSecondTaskIntentSet();
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
} else {
return isInitialTaskIntentSet() && isSecondTaskIntentSet();
}
}
private boolean isInitialTaskIntentSet() {
@@ -609,11 +758,19 @@ public class SplitSelectStateController {
}
public int getInitialTaskId() {
return mInitialTaskId;
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
return mSplitSelectDataHolder.getInitialTaskId();
} else {
return mInitialTaskId;
}
}
public int getSecondTaskId() {
return mSecondTaskId;
if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
return mSplitSelectDataHolder.getSecondTaskId();
} else {
return mSecondTaskId;
}
}
private boolean isSecondTaskIntentSet() {
@@ -632,4 +789,10 @@ public class SplitSelectStateController {
public AppPairsController getAppPairsController() {
return mAppPairsController;
}
public void dump(String prefix, PrintWriter writer) {
if (mSplitSelectDataHolder != null) {
mSplitSelectDataHolder.dump(prefix, writer);
}
}
}
@@ -407,7 +407,12 @@ public final class FeatureFlags {
"USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
"Use local overrides for search request timeout");
// TODO(Block 31): Empty block
// TODO(Block 31)
public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
"ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", DISABLED,
"Use refactored split launching code path");
// TODO(Block 32): Empty block
public static class BooleanFlag {
@@ -111,6 +111,7 @@ public final class TestProtocol {
public static final String REQUEST_IS_TABLET = "is-tablet";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready";
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
"get-activities-created-count";
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
@@ -16,11 +16,14 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT;
import android.graphics.Point;
@@ -146,6 +149,12 @@ public final class LaunchedAppState extends Background {
try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
"started item drag")) {
launcher.assertTrue("Shell drag not marked as ready", launcher.waitAndGet(() -> {
LauncherInstrumentation.log("Checking shell drag ready");
return launcher.getTestInfo(REQUEST_SHELL_DRAG_READY)
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
}, WAIT_TIME_MS, DEFAULT_POLL_INTERVAL));
launcher.movePointer(
dragStart,
endPoint,