diff options
39 files changed, 1405 insertions, 522 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 26f1c4b146a5..9c279c3b8254 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -2563,11 +2563,14 @@ public class ActivityOptions extends ComponentOptions { public static final int TYPE_LOCKSCREEN = 3; /** Launched from recents gesture handler. */ public static final int TYPE_RECENTS_ANIMATION = 4; + /** Launched from desktop's transition handler. */ + public static final int TYPE_DESKTOP_ANIMATION = 5; @IntDef(prefix = { "TYPE_" }, value = { TYPE_LAUNCHER, TYPE_NOTIFICATION, TYPE_LOCKSCREEN, + TYPE_DESKTOP_ANIMATION }) @Retention(RetentionPolicy.SOURCE) public @interface SourceType {} diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 0b8d1dfccc07..6b558d07c059 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -184,6 +184,14 @@ public abstract class BackupAgent extends ContextWrapper { public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; /** + * Flag for {@link RestoreSet#backupTransportFlags} to indicate if restore should be skipped + * for apps that have already been launched. + * + * @hide + */ + public static final int FLAG_SKIP_RESTORE_FOR_LAUNCHED_APPS = 1 << 2; + + /** * Flag for {@link BackupDataOutput#getTransportFlags()} and * {@link FullBackupDataOutput#getTransportFlags()} only. * diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index acdc0fac0e98..d33eadcfd11b 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -27,7 +27,6 @@ import android.os.ParcelFileDescriptor; * <p> * This class is not thread-safe. * </p> - * Note that this class is unrelated to {@link SQLiteRawStatement}. */ public final class SQLiteStatement extends SQLiteProgram { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index 0a6be20226b8..eda80c861698 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -85,14 +85,14 @@ oneway interface INetworkManagementEventObserver { /** * Interface data activity status is changed. * - * @param transportType The transport type of the data activity change. + * @param label label of the data activity change. * @param active True if the interface is actively transmitting data, false if it is idle. * @param tsNanos Elapsed realtime in nanos when the state of the network interface changed. * @param uid Uid of this event. It represents the uid that was responsible for waking the * radio. For those events that are reported by system itself, not from specific uid, * use -1 for the events which means no uid. */ - void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, int uid); + void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos, int uid); /** * Information about available DNS servers has been received. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index daec1721977b..13572fb1bbbb 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -832,10 +832,16 @@ public class Process { /** * Returns true if the current process is a 64-bit runtime. */ + @android.ravenwood.annotation.RavenwoodReplace public static final boolean is64Bit() { return VMRuntime.getRuntime().is64Bit(); } + /** @hide */ + public static final boolean is64Bit$ravenwood() { + return "amd64".equals(System.getProperty("os.arch")); + } + private static SomeArgs sIdentity$ravenwood; /** @hide */ @@ -906,6 +912,7 @@ public class Process { * {@link #myUid()} in that a particular user will have multiple * distinct apps running under it each with their own uid. */ + @android.ravenwood.annotation.RavenwoodKeep public static UserHandle myUserHandle() { return UserHandle.of(UserHandle.getUserId(myUid())); } @@ -914,6 +921,7 @@ public class Process { * Returns whether the given uid belongs to a system core component or not. * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static boolean isCoreUid(int uid) { return UserHandle.isCore(uid); } @@ -924,6 +932,7 @@ public class Process { * @return Whether the uid corresponds to an application sandbox running in * a specific user. */ + @android.ravenwood.annotation.RavenwoodKeep public static boolean isApplicationUid(int uid) { return UserHandle.isApp(uid); } @@ -931,6 +940,7 @@ public class Process { /** * Returns whether the current process is in an isolated sandbox. */ + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isIsolated() { return isIsolated(myUid()); } @@ -942,6 +952,7 @@ public class Process { @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.") + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isIsolated(int uid) { return isIsolatedUid(uid); } @@ -949,6 +960,7 @@ public class Process { /** * Returns whether the process with the given {@code uid} is an isolated sandbox. */ + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isIsolatedUid(int uid) { uid = UserHandle.getAppId(uid); return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID) @@ -962,6 +974,7 @@ public class Process { */ @SystemApi(client = MODULE_LIBRARIES) @TestApi + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isSdkSandboxUid(int uid) { uid = UserHandle.getAppId(uid); return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID); @@ -975,6 +988,7 @@ public class Process { */ @SystemApi(client = MODULE_LIBRARIES) @TestApi + @android.ravenwood.annotation.RavenwoodKeep public static final int getAppUidForSdkSandboxUid(int uid) { return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID); } @@ -987,6 +1001,7 @@ public class Process { */ @SystemApi(client = MODULE_LIBRARIES) @TestApi + @android.ravenwood.annotation.RavenwoodKeep public static final int toSdkSandboxUid(int uid) { return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID); } @@ -994,6 +1009,7 @@ public class Process { /** * Returns whether the current process is a sdk sandbox process. */ + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isSdkSandbox() { return isSdkSandboxUid(myUid()); } diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index cac7f3b74185..0644ef1c788f 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -36,6 +36,7 @@ import java.util.Random; /** * Representation of a user on the device. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class UserHandle implements Parcelable { // NOTE: keep logic in sync with system/core/libcutils/multiuser.c diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java index 33058d84b2e5..2a33caaf7e28 100644 --- a/core/java/android/util/Xml.java +++ b/core/java/android/util/Xml.java @@ -26,6 +26,7 @@ import com.android.internal.util.ArtBinaryXmlPullParser; import com.android.internal.util.ArtBinaryXmlSerializer; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; +import com.android.modules.utils.BinaryXmlPullParser; import com.android.modules.utils.BinaryXmlSerializer; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -38,6 +39,7 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; import java.io.BufferedInputStream; @@ -115,6 +117,7 @@ public class Xml { /** * Returns a new pull parser with namespace support. */ + @android.ravenwood.annotation.RavenwoodReplace public static XmlPullParser newPullParser() { try { XmlPullParser parser = XmlObjectFactory.newXmlPullParser(); @@ -126,6 +129,12 @@ public class Xml { } } + /** @hide */ + public static XmlPullParser newPullParser$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlPullParser(); + } + /** * Creates a new {@link TypedXmlPullParser} which is optimized for use * inside the system, typically by supporting only a basic set of features. @@ -136,10 +145,17 @@ public class Xml { * @hide */ @SuppressWarnings("AndroidFrameworkEfficientXml") + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlPullParser newFastPullParser() { return XmlUtils.makeTyped(newPullParser()); } + /** @hide */ + public static TypedXmlPullParser newFastPullParser$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlPullParser(); + } + /** * Creates a new {@link XmlPullParser} that reads XML documents using a * custom binary wire protocol which benchmarking has shown to be 8.5x @@ -148,10 +164,17 @@ public class Xml { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlPullParser newBinaryPullParser() { return new ArtBinaryXmlPullParser(); } + /** @hide */ + public static TypedXmlPullParser newBinaryPullParser$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlPullParser(); + } + /** * Creates a new {@link XmlPullParser} which is optimized for use inside the * system, typically by supporting only a basic set of features. @@ -166,6 +189,7 @@ public class Xml { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in) throws IOException { final byte[] magic = new byte[4]; @@ -198,13 +222,33 @@ public class Xml { return xml; } + /** @hide */ + public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in) + throws IOException { + // TODO: remove once we're linking against libcore + final TypedXmlPullParser xml = new BinaryXmlPullParser(); + try { + xml.setInput(in, StandardCharsets.UTF_8.name()); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + return xml; + } + /** * Creates a new xml serializer. */ + @android.ravenwood.annotation.RavenwoodReplace public static XmlSerializer newSerializer() { return XmlObjectFactory.newXmlSerializer(); } + /** @hide */ + public static XmlSerializer newSerializer$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlSerializer(); + } + /** * Creates a new {@link XmlSerializer} which is optimized for use inside the * system, typically by supporting only a basic set of features. @@ -215,10 +259,17 @@ public class Xml { * @hide */ @SuppressWarnings("AndroidFrameworkEfficientXml") + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlSerializer newFastSerializer() { return XmlUtils.makeTyped(new FastXmlSerializer()); } + /** @hide */ + public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlSerializer(); + } + /** * Creates a new {@link XmlSerializer} that writes XML documents using a * custom binary wire protocol which benchmarking has shown to be 4.4x @@ -227,10 +278,17 @@ public class Xml { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlSerializer newBinarySerializer() { return new ArtBinaryXmlSerializer(); } + /** @hide */ + public static @NonNull TypedXmlSerializer newBinarySerializer$ravenwood() { + // TODO: remove once we're linking against libcore + return new BinaryXmlSerializer(); + } + /** * Creates a new {@link XmlSerializer} which is optimized for use inside the * system, typically by supporting only a basic set of features. @@ -245,6 +303,7 @@ public class Xml { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out) throws IOException { final TypedXmlSerializer xml; @@ -257,6 +316,15 @@ public class Xml { return xml; } + /** @hide */ + public static @NonNull TypedXmlSerializer resolveSerializer$ravenwood(@NonNull OutputStream out) + throws IOException { + // TODO: remove once we're linking against libcore + final TypedXmlSerializer xml = new BinaryXmlSerializer(); + xml.setOutput(out, StandardCharsets.UTF_8.name()); + return xml; + } + /** * Copy the first XML document into the second document. * <p> diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java index 3e8916caead9..768ea82e0c71 100644 --- a/core/java/com/android/internal/util/ArtFastDataInput.java +++ b/core/java/com/android/internal/util/ArtFastDataInput.java @@ -21,6 +21,8 @@ import android.util.CharsetUtils; import com.android.modules.utils.FastDataInput; +import dalvik.system.VMRuntime; + import java.io.DataInput; import java.io.IOException; import java.io.InputStream; @@ -35,13 +37,14 @@ import java.util.concurrent.atomic.AtomicReference; */ public class ArtFastDataInput extends FastDataInput { private static AtomicReference<ArtFastDataInput> sInCache = new AtomicReference<>(); + private static VMRuntime sRuntime = VMRuntime.getRuntime(); private final long mBufferPtr; public ArtFastDataInput(@NonNull InputStream in, int bufferSize) { super(in, bufferSize); - mBufferPtr = mRuntime.addressOf(mBuffer); + mBufferPtr = sRuntime.addressOf(mBuffer); } /** @@ -66,6 +69,7 @@ public class ArtFastDataInput extends FastDataInput { * Release a {@link ArtFastDataInput} to potentially be recycled. You must not * interact with the object after releasing it. */ + @Override public void release() { super.release(); @@ -76,6 +80,11 @@ public class ArtFastDataInput extends FastDataInput { } @Override + public byte[] newByteArray(int bufferSize) { + return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize); + } + + @Override public String readUTF() throws IOException { // Attempt to read directly from buffer space if there's enough room, // otherwise fall back to chunking into place @@ -86,9 +95,9 @@ public class ArtFastDataInput extends FastDataInput { mBufferPos += len; return res; } else { - final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); + final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1); readFully(tmp, 0, len); - return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len); + return CharsetUtils.fromModifiedUtf8Bytes(sRuntime.addressOf(tmp), 0, len); } } } diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java index ac595b6fd151..360ddb814aa2 100644 --- a/core/java/com/android/internal/util/ArtFastDataOutput.java +++ b/core/java/com/android/internal/util/ArtFastDataOutput.java @@ -21,6 +21,8 @@ import android.util.CharsetUtils; import com.android.modules.utils.FastDataOutput; +import dalvik.system.VMRuntime; + import java.io.DataOutput; import java.io.IOException; import java.io.OutputStream; @@ -35,13 +37,14 @@ import java.util.concurrent.atomic.AtomicReference; */ public class ArtFastDataOutput extends FastDataOutput { private static AtomicReference<ArtFastDataOutput> sOutCache = new AtomicReference<>(); + private static VMRuntime sRuntime = VMRuntime.getRuntime(); private final long mBufferPtr; public ArtFastDataOutput(@NonNull OutputStream out, int bufferSize) { super(out, bufferSize); - mBufferPtr = mRuntime.addressOf(mBuffer); + mBufferPtr = sRuntime.addressOf(mBuffer); } /** @@ -73,6 +76,11 @@ public class ArtFastDataOutput extends FastDataOutput { } @Override + public byte[] newByteArray(int bufferSize) { + return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize); + } + + @Override public void writeUTF(String s) throws IOException { // Attempt to write directly to buffer space if there's enough room, // otherwise fall back to chunking into place @@ -94,8 +102,8 @@ public class ArtFastDataOutput extends FastDataOutput { // Negative value indicates buffer was too small and we need to // allocate a temporary buffer for encoding len = -len; - final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); - CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length); + final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1); + CharsetUtils.toModifiedUtf8Bytes(s, sRuntime.addressOf(tmp), 0, tmp.length); writeShort(len); write(tmp, 0, len); } diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java index 139b88b108c5..61e017d3443f 100644 --- a/core/java/com/android/server/net/BaseNetworkObserver.java +++ b/core/java/com/android/server/net/BaseNetworkObserver.java @@ -64,7 +64,7 @@ public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { } @Override - public void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, + public void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos, int uid) { // default no-op } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index ab61a48a715c..5143d419597b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -115,6 +115,19 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { b.setParent(sc); } + /** + * Re-parents the provided surface to the leash of the provided display. + * + * @param displayId the display area to reparent to. + * @param sc the surface to be reparented. + * @param t a {@link SurfaceControl.Transaction} in which to reparent. + */ + public void reparentToDisplayArea(int displayId, SurfaceControl sc, + SurfaceControl.Transaction t) { + final SurfaceControl displayAreaLeash = mLeashes.get(displayId); + t.reparent(sc, displayAreaLeash); + } + public void setPosition(@NonNull SurfaceControl.Transaction tx, int displayId, int x, int y) { final SurfaceControl sc = mLeashes.get(displayId); if (sc == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 47769a8eeb11..71bf487249fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -59,6 +59,7 @@ import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; @@ -498,6 +499,7 @@ public abstract class WMShellModule { EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, + DragToDesktopTransitionHandler dragToDesktopTransitionHandler, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, LaunchAdjacentController launchAdjacentController, RecentsTransitionHandler recentsTransitionHandler, @@ -506,8 +508,19 @@ public abstract class WMShellModule { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, - toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, - launchAdjacentController, recentsTransitionHandler, mainExecutor); + toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, + desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler, + mainExecutor); + } + + @WMSingleton + @Provides + static DragToDesktopTransitionHandler provideDragToDesktopTransitionHandler( + Context context, + Transitions transitions, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + return new DragToDesktopTransitionHandler(context, transitions, + rootTaskDisplayAreaOrganizer); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 8e12991080ed..4a9ea6fed73f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -59,6 +59,7 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener @@ -92,6 +93,7 @@ class DesktopTasksController( private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, + private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopModeTaskRepository: DesktopModeTaskRepository, private val launchAdjacentController: LaunchAdjacentController, private val recentsTransitionHandler: RecentsTransitionHandler, @@ -110,6 +112,20 @@ class DesktopTasksController( launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks } } + private val dragToDesktopStateListener = object : DragToDesktopStateListener { + override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) { + removeVisualIndicator(tx) + } + + override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) { + removeVisualIndicator(tx) + } + + private fun removeVisualIndicator(tx: SurfaceControl.Transaction) { + visualIndicator?.releaseVisualIndicator(tx) + visualIndicator = null + } + } private val transitionAreaHeight get() = context.resources.getDimensionPixelSize( @@ -122,9 +138,7 @@ class DesktopTasksController( ) private var recentsAnimationRunning = false - - // This is public to avoid cyclic dependency; it is set by SplitScreenController - lateinit var splitScreenController: SplitScreenController + private lateinit var splitScreenController: SplitScreenController init { desktopMode = DesktopModeImpl() @@ -143,7 +157,7 @@ class DesktopTasksController( ) transitions.addHandler(this) desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor) - + dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener) recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { override fun onAnimationStateChanged(running: Boolean) { @@ -158,6 +172,12 @@ class DesktopTasksController( ) } + /** Setter needed to avoid cyclic dependency. */ + fun setSplitScreenController(controller: SplitScreenController) { + splitScreenController = controller + dragToDesktopTransitionHandler.setSplitScreenController(controller) + } + /** Show all tasks, that are part of the desktop, on top of launcher */ fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps") @@ -248,56 +268,43 @@ class DesktopTasksController( } /** - * The first part of the animated move to desktop transition. Applies the changes to move task - * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is - * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}. + * The first part of the animated drag to desktop transition. This is + * followed with a call to [finalizeDragToDesktop] or [cancelDragToDesktop]. */ - fun startMoveToDesktop( + fun startDragToDesktop( taskInfo: RunningTaskInfo, - startBounds: Rect, - dragToDesktopValueAnimator: MoveToDesktopAnimator + dragToDesktopValueAnimator: MoveToDesktopAnimator, + windowDecor: DesktopModeWindowDecoration ) { KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: startMoveToDesktop taskId=%d", - taskInfo.taskId + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: startDragToDesktop taskId=%d", + taskInfo.taskId + ) + dragToDesktopTransitionHandler.startDragToDesktopTransition( + taskInfo.taskId, + dragToDesktopValueAnimator, + windowDecor ) - val wct = WindowContainerTransaction() - exitSplitIfApplicable(wct, taskInfo) - moveHomeTaskToFront(wct) - addMoveToDesktopChanges(wct, taskInfo) - wct.setBounds(taskInfo.token, startBounds) - - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator, - mOnAnimationFinishedCallback) - } else { - shellTaskOrganizer.applyTransaction(wct) - } } /** - * The second part of the animated move to desktop transition, called after - * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds. + * The second part of the animated drag to desktop transition, called after + * [startDragToDesktop]. */ - private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { + private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: finalizeMoveToDesktop taskId=%d", - taskInfo.taskId + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: finalizeDragToDesktop taskId=%d", + taskInfo.taskId ) val wct = WindowContainerTransaction() + exitSplitIfApplicable(wct, taskInfo) + moveHomeTaskToFront(wct) bringDesktopAppsToFront(taskInfo.displayId, wct) addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) - - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, - mOnAnimationFinishedCallback) - } else { - shellTaskOrganizer.applyTransaction(wct) - releaseVisualIndicator() - } + dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) } /** @@ -353,40 +360,40 @@ class DesktopTasksController( } private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { - if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { - splitScreenController.prepareExitSplitScreen(wct, - splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP) + if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) { + splitScreenController.prepareExitSplitScreen( + wct, + splitScreenController.getStageOfTask(taskInfo.taskId), + EXIT_REASON_ENTER_DESKTOP + ) + getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo -> + wct.removeTask(otherTaskInfo.token) + } } } + private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? { + val remainingTaskPosition: Int = + if (splitScreenController.getSplitPosition(taskId) + == SPLIT_POSITION_BOTTOM_OR_RIGHT) { + SPLIT_POSITION_TOP_OR_LEFT + } else { + SPLIT_POSITION_BOTTOM_OR_RIGHT + } + return splitScreenController.getTaskInfo(remainingTaskPosition) + } + /** - * The second part of the animated move to desktop transition, called after - * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen - * and released back into status bar area. + * The second part of the animated drag to desktop transition, called after + * [startDragToDesktop]. */ - fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { + fun cancelDragToDesktop(task: RunningTaskInfo) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: cancelMoveToDesktop taskId=%d", + "DesktopTasksController: cancelDragToDesktop taskId=%d", task.taskId ) - val wct = WindowContainerTransaction() - wct.setBounds(task.token, Rect()) - - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, - moveToDesktopAnimator) { t -> - val callbackWCT = WindowContainerTransaction() - visualIndicator?.releaseVisualIndicator(t) - visualIndicator = null - addMoveToFullscreenChanges(callbackWCT, task) - transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */) - } - } else { - addMoveToFullscreenChanges(wct, task) - shellTaskOrganizer.applyTransaction(wct) - releaseVisualIndicator() - } + dragToDesktopTransitionHandler.cancelDragToDesktopTransition() } private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) { @@ -966,6 +973,11 @@ class DesktopTasksController( visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, shellTaskOrganizer, rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR) + // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has + // started, or it'll be visible too early on top of the task surface, especially in + // the cancel-early case. Also because it shouldn't even be shown in the cancel-early + // case since its dismissal is tied to the cancel animation end, which doesn't even run + // in cancel-early. visualIndicator?.createIndicatorWithAnimatedBounds() } val indicator = visualIndicator ?: return @@ -988,7 +1000,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, freeformBounds: Rect ) { - finalizeMoveToDesktop(taskInfo, freeformBounds) + finalizeDragToDesktop(taskInfo, freeformBounds) } private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt new file mode 100644 index 000000000000..75d27d99b9ac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -0,0 +1,543 @@ +package com.android.wm.shell.desktopmode + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.app.ActivityOptions +import android.app.ActivityOptions.SourceInfo +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT +import android.app.PendingIntent.FLAG_MUTABLE +import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context +import android.content.Intent +import android.content.Intent.FILL_IN_COMPONENT +import android.graphics.Rect +import android.os.IBinder +import android.os.SystemClock +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CLOSE +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import android.window.TransitionRequestInfo +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.splitscreen.SplitScreenController +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TransitionHandler +import com.android.wm.shell.util.TransitionUtil +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE +import java.util.function.Supplier + +/** + * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also + * handles the cancellation case where the task is dragged back to the status bar area in the same + * gesture. + */ +class DragToDesktopTransitionHandler( + private val context: Context, + private val transitions: Transitions, + private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + private val transactionSupplier: Supplier<SurfaceControl.Transaction> +) : TransitionHandler { + + constructor( + context: Context, + transitions: Transitions, + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + ) : this( + context, + transitions, + rootTaskDisplayAreaOrganizer, + Supplier { SurfaceControl.Transaction() } + ) + + private val rectEvaluator = RectEvaluator(Rect()) + private val launchHomeIntent = Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + + private var dragToDesktopStateListener: DragToDesktopStateListener? = null + private var splitScreenController: SplitScreenController? = null + private var transitionState: TransitionState? = null + + /** Sets a listener to receive callback about events during the transition animation. */ + fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) { + dragToDesktopStateListener = listener + } + + /** Setter needed to avoid cyclic dependency. */ + fun setSplitScreenController(controller: SplitScreenController) { + splitScreenController = controller + } + + /** + * Starts a transition that performs a transient launch of Home so that Home is brought to the + * front while still keeping the currently focused task that is being dragged resumed. This + * allows the animation handler to reorder the task to the front and to scale it with the + * gesture into the desktop area with the Home and wallpaper behind it. + * + * Note that the transition handler for this transition doesn't call the finish callback until + * after one of the "end" or "cancel" transitions is merged into this transition. + */ + fun startDragToDesktopTransition( + taskId: Int, + dragToDesktopAnimator: MoveToDesktopAnimator, + windowDecoration: DesktopModeWindowDecoration + ) { + if (transitionState != null) { + error("A drag to desktop is already in progress") + } + + val options = ActivityOptions.makeBasic().apply { + setTransientLaunch() + setSourceInfo(SourceInfo.TYPE_DESKTOP_ANIMATION, SystemClock.uptimeMillis()) + } + val pendingIntent = PendingIntent.getActivity( + context, + 0 /* requestCode */, + launchHomeIntent, + FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT + ) + val wct = WindowContainerTransaction() + wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) + val startTransitionToken = transitions + .startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this) + + transitionState = if (isSplitTask(taskId)) { + TransitionState.FromSplit( + draggedTaskId = taskId, + dragAnimator = dragToDesktopAnimator, + windowDecoration = windowDecoration, + startTransitionToken = startTransitionToken + ) + } else { + TransitionState.FromFullscreen( + draggedTaskId = taskId, + dragAnimator = dragToDesktopAnimator, + windowDecoration = windowDecoration, + startTransitionToken = startTransitionToken + ) + } + } + + /** + * Starts a transition that "finishes" the drag to desktop gesture. This transition is intended + * to merge into the "start" transition and is the one that actually applies the bounds and + * windowing mode changes to the dragged task. This is called when the dragged task is released + * inside the desktop drop zone. + */ + fun finishDragToDesktopTransition(wct: WindowContainerTransaction) { + transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) + } + + /** + * Starts a transition that "cancels" the drag to desktop gesture. This transition is intended + * to merge into the "start" transition and it restores the transient state that was used to + * launch the Home task over the dragged task. This is called when the dragged task is released + * outside the desktop drop zone and is instead dropped back into the status bar region that + * means the user wants to remain in their current windowing mode. + */ + fun cancelDragToDesktopTransition() { + val state = requireTransitionState() + state.cancelled = true + if (state.draggedTaskChange != null) { + // Regular case, transient launch of Home happened as is waiting for the cancel + // transient to start and merge. Animate the cancellation (scale back to original + // bounds) first before actually starting the cancel transition so that the wallpaper + // is visible behind the animating task. + startCancelAnimation() + } else { + // There's no dragged task, this can happen when the "cancel" happened too quickly + // before the "start" transition is even ready (like on a fling gesture). The + // "shrink" animation didn't even start, so there's no need to animate the "cancel". + // We also don't want to start the cancel transition yet since we don't have + // enough info to restore the order. We'll check for the cancelled state flag when + // the "start" animation is ready and cancel from #startAnimation instead. + } + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + val state = requireTransitionState() + + val isStartDragToDesktop = info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP && + transition == state.startTransitionToken + if (!isStartDragToDesktop) { + return false + } + + // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom, + // then Home on top of that, wallpaper on top of that and finally the dragged task on top + // of everything. + val appLayers = info.changes.size + val homeLayers = info.changes.size * 2 + val wallpaperLayers = info.changes.size * 3 + val dragLayer = wallpaperLayers + val leafTaskFilter = TransitionUtil.LeafTaskFilter() + info.changes.withIndex().forEach { (i, change) -> + if (TransitionUtil.isWallpaper(change)) { + val layer = wallpaperLayers - i + startTransaction.apply { + setLayer(change.leash, layer) + show(change.leash) + } + } else if (isHomeChange(change)) { + state.homeToken = change.container + val layer = homeLayers - i + startTransaction.apply { + setLayer(change.leash, layer) + show(change.leash) + } + } else if (TransitionInfo.isIndependent(change, info)) { + // Root. + when (state) { + is TransitionState.FromSplit -> { + state.splitRootChange = change + val layer = if (!state.cancelled) { + // Normal case, split root goes to the bottom behind everything else. + appLayers - i + } else { + // Cancel-early case, pretend nothing happened so split root stays top. + dragLayer + } + startTransaction.apply { + setLayer(change.leash, layer) + show(change.leash) + } + } + is TransitionState.FromFullscreen -> { + if (change.taskInfo?.taskId == state.draggedTaskId) { + state.draggedTaskChange = change + val bounds = change.endAbsBounds + startTransaction.apply { + setLayer(change.leash, dragLayer) + setWindowCrop(change.leash, bounds.width(), bounds.height()) + show(change.leash) + } + } else { + throw IllegalStateException("Expected root to be dragged task") + } + } + } + } else if (leafTaskFilter.test(change)) { + // When dragging one of the split tasks, the dragged leaf needs to be re-parented + // so that it can be layered separately from the rest of the split root/stages. + // The split root including the other split side was layered behind the wallpaper + // and home while the dragged split needs to be layered in front of them. + // Do not do this in the cancel-early case though, since in that case nothing should + // happen on screen so the layering will remain the same as if no transition + // occurred. + if (change.taskInfo?.taskId == state.draggedTaskId && !state.cancelled) { + state.draggedTaskChange = change + taskDisplayAreaOrganizer.reparentToDisplayArea( + change.endDisplayId, change.leash, startTransaction) + val bounds = change.endAbsBounds + startTransaction.apply { + setLayer(change.leash, dragLayer) + setWindowCrop(change.leash, bounds.width(), bounds.height()) + show(change.leash) + } + } + } + } + state.startTransitionFinishCb = finishCallback + state.startTransitionFinishTransaction = finishTransaction + startTransaction.apply() + + if (!state.cancelled) { + // Normal case, start animation to scale down the dragged task. It'll also be moved to + // follow the finger and when released we'll start the next phase/transition. + state.dragAnimator.startAnimation() + } else { + // Cancel-early case, the state was flagged was cancelled already, which means the + // gesture ended in the cancel region. This can happen even before the start transition + // is ready/animate here when cancelling quickly like with a fling. There's no point + // in starting the scale down animation that we would scale up anyway, so just jump + // directly into starting the cancel transition to restore WM order. Surfaces should + // not move as if no transition happened. + startCancelDragToDesktopTransition() + } + return true + } + + override fun mergeAnimation( + transition: IBinder, + info: TransitionInfo, + t: SurfaceControl.Transaction, + mergeTarget: IBinder, + finishCallback: Transitions.TransitionFinishCallback + ) { + val state = requireTransitionState() + val isCancelTransition = info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP && + transition == state.cancelTransitionToken && + mergeTarget == state.startTransitionToken + val isEndTransition = info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP && + mergeTarget == state.startTransitionToken + + val startTransactionFinishT = state.startTransitionFinishTransaction + ?: error("Start transition expected to be waiting for merge but wasn't") + val startTransitionFinishCb = state.startTransitionFinishCb + ?: error("Start transition expected to be waiting for merge but wasn't") + if (isEndTransition) { + info.changes.withIndex().forEach { (i, change) -> + if (change.mode == TRANSIT_CLOSE) { + t.hide(change.leash) + startTransactionFinishT.hide(change.leash) + } else if (change.taskInfo?.taskId == state.draggedTaskId) { + t.show(change.leash) + startTransactionFinishT.show(change.leash) + state.draggedTaskChange = change + } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) { + // Other freeform tasks that are being restored go behind the dragged task. + val draggedTaskLeash = state.draggedTaskChange?.leash + ?: error("Expected dragged leash to be non-null") + t.setRelativeLayer(change.leash, draggedTaskLeash, -i) + startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i) + } + } + + val draggedTaskChange = state.draggedTaskChange + ?: throw IllegalStateException("Expected non-null change of dragged task") + val draggedTaskLeash = draggedTaskChange.leash + val startBounds = draggedTaskChange.startAbsBounds + val endBounds = draggedTaskChange.endAbsBounds + + // TODO(b/301106941): Instead of forcing-finishing the animation that scales the + // surface down and then starting another that scales it back up to the final size, + // blend the two animations. + state.dragAnimator.endAnimator() + // Using [DRAG_FREEFORM_SCALE] to calculate animated width/height is possible because + // it is known that the animation scale is finished because the animation was + // force-ended above. This won't be true when the two animations are blended. + val animStartWidth = (startBounds.width() * DRAG_FREEFORM_SCALE).toInt() + val animStartHeight = (startBounds.height() * DRAG_FREEFORM_SCALE).toInt() + // Using end bounds here to find the left/top also assumes the center animation has + // finished and the surface is placed exactly in the center of the screen which matches + // the end/default bounds of the now freeform task. + val animStartLeft = endBounds.centerX() - (animStartWidth / 2) + val animStartTop = endBounds.centerY() - (animStartHeight / 2) + val animStartBounds = Rect( + animStartLeft, + animStartTop, + animStartLeft + animStartWidth, + animStartTop + animStartHeight + ) + + + dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t) + t.apply { + setScale(draggedTaskLeash, 1f, 1f) + setPosition( + draggedTaskLeash, + animStartBounds.left.toFloat(), + animStartBounds.top.toFloat() + ) + setWindowCrop( + draggedTaskLeash, + animStartBounds.width(), + animStartBounds.height() + ) + } + // Accept the merge by applying the merging transaction (applied by #showResizeVeil) + // and finish callback. Show the veil and position the task at the first frame before + // starting the final animation. + state.windowDecoration.showResizeVeil(t, animStartBounds) + finishCallback.onTransitionFinished(null /* wct */) + + // Because the task surface was scaled down during the drag, we must use the animated + // bounds instead of the [startAbsBounds]. + val tx: SurfaceControl.Transaction = transactionSupplier.get() + ValueAnimator.ofObject(rectEvaluator, animStartBounds, endBounds) + .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) + .apply { + addUpdateListener { animator -> + val animBounds = animator.animatedValue as Rect + tx.apply { + setScale(draggedTaskLeash, 1f, 1f) + setPosition( + draggedTaskLeash, + animBounds.left.toFloat(), + animBounds.top.toFloat() + ) + setWindowCrop( + draggedTaskLeash, + animBounds.width(), + animBounds.height() + ) + } + state.windowDecoration.updateResizeVeil(tx, animBounds) + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + state.windowDecoration.hideResizeVeil() + startTransitionFinishCb.onTransitionFinished(null /* null */) + clearState() + } + }) + start() + } + } else if (isCancelTransition) { + info.changes.forEach { change -> + t.show(change.leash) + startTransactionFinishT.show(change.leash) + } + t.apply() + finishCallback.onTransitionFinished(null /* wct */) + startTransitionFinishCb.onTransitionFinished(null /* wct */) + clearState() + } + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + // Only handle transitions started from shell. + return null + } + + private fun isHomeChange(change: Change): Boolean { + return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME + } + + private fun startCancelAnimation() { + val state = requireTransitionState() + val dragToDesktopAnimator = state.dragAnimator + + val draggedTaskChange = state.draggedTaskChange + ?: throw IllegalStateException("Expected non-null task change") + val sc = draggedTaskChange.leash + // TODO(b/301106941): Don't end the animation and start one to scale it back, merge them + // instead. + // End the animation that shrinks the window when task is first dragged from fullscreen + dragToDesktopAnimator.endAnimator() + // Then animate the scaled window back to its original bounds. + val x: Float = dragToDesktopAnimator.position.x + val y: Float = dragToDesktopAnimator.position.y + val targetX = draggedTaskChange.endAbsBounds.left + val targetY = draggedTaskChange.endAbsBounds.top + val dx = targetX - x + val dy = targetY - y + val tx: SurfaceControl.Transaction = transactionSupplier.get() + ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f) + .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) + .apply { + addUpdateListener { animator -> + val scale = animator.animatedValue as Float + val fraction = animator.animatedFraction + val animX = x + (dx * fraction) + val animY = y + (dy * fraction) + tx.apply { + setPosition(sc, animX, animY) + setScale(sc, scale, scale) + show(sc) + apply() + } + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + dragToDesktopStateListener?.onCancelToDesktopAnimationEnd(tx) + // Start the cancel transition to restore order. + startCancelDragToDesktopTransition() + } + }) + start() + } + } + + private fun startCancelDragToDesktopTransition() { + val state = requireTransitionState() + val wct = WindowContainerTransaction() + when (state) { + is TransitionState.FromFullscreen -> { + val wc = state.draggedTaskChange?.container + ?: error("Dragged task should be non-null before cancelling") + wct.reorder(wc, true /* toTop */) + } + is TransitionState.FromSplit -> { + val wc = state.splitRootChange?.container + ?: error("Split root should be non-null before cancelling") + wct.reorder(wc, true /* toTop */) + } + } + val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling") + wct.restoreTransientOrder(homeWc) + + state.cancelTransitionToken = transitions.startTransition( + TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this) + } + + private fun clearState() { + transitionState = null + } + + private fun isSplitTask(taskId: Int): Boolean { + return splitScreenController?.isTaskInSplitScreen(taskId) ?: false + } + + private fun requireTransitionState(): TransitionState { + return transitionState ?: error("Expected non-null transition state") + } + + interface DragToDesktopStateListener { + fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) + fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) + } + + sealed class TransitionState { + abstract val draggedTaskId: Int + abstract val dragAnimator: MoveToDesktopAnimator + abstract val windowDecoration: DesktopModeWindowDecoration + abstract val startTransitionToken: IBinder + abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback? + abstract var startTransitionFinishTransaction: SurfaceControl.Transaction? + abstract var cancelTransitionToken: IBinder? + abstract var homeToken: WindowContainerToken? + abstract var draggedTaskChange: Change? + abstract var cancelled: Boolean + + data class FromFullscreen( + override val draggedTaskId: Int, + override val dragAnimator: MoveToDesktopAnimator, + override val windowDecoration: DesktopModeWindowDecoration, + override val startTransitionToken: IBinder, + override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null, + override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null, + override var cancelTransitionToken: IBinder? = null, + override var homeToken: WindowContainerToken? = null, + override var draggedTaskChange: Change? = null, + override var cancelled: Boolean = false, + ) : TransitionState() + data class FromSplit( + override val draggedTaskId: Int, + override val dragAnimator: MoveToDesktopAnimator, + override val windowDecoration: DesktopModeWindowDecoration, + override val startTransitionToken: IBinder, + override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null, + override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null, + override var cancelTransitionToken: IBinder? = null, + override var homeToken: WindowContainerToken? = null, + override var draggedTaskChange: Change? = null, + override var cancelled: Boolean = false, + var splitRootChange: Change? = null, + ) : TransitionState() + } + + companion object { + /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ + private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 024465b281b8..605600f54823 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -18,12 +18,13 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_TO_DESKTOP; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.app.ActivityManager; -import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; import android.util.Slog; @@ -38,11 +39,9 @@ import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration; -import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -60,8 +59,6 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); - private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; - private MoveToDesktopAnimator mMoveToDesktopAnimator; private DesktopModeWindowDecoration mDesktopModeWindowDecoration; public EnterDesktopTaskTransitionHandler( @@ -77,61 +74,6 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } /** - * Starts Transition of a given type - * @param type Transition type - * @param wct WindowContainerTransaction for transition - * @param onAnimationEndCallback to be called after animation - */ - private void startTransition(@WindowManager.TransitionType int type, - @NonNull WindowContainerTransaction wct, - Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - mOnAnimationFinishedCallback = onAnimationEndCallback; - final IBinder token = mTransitions.startTransition(type, wct, this); - mPendingTransitionTokens.add(token); - } - - /** - * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE - * @param wct WindowContainerTransaction for transition - * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move - * to desktop animation - * @param onAnimationEndCallback to be called after animation - */ - public void startMoveToDesktop(@NonNull WindowContainerTransaction wct, - @NonNull MoveToDesktopAnimator moveToDesktopAnimator, - Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - mMoveToDesktopAnimator = moveToDesktopAnimator; - startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct, - onAnimationEndCallback); - } - - /** - * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE - * @param wct WindowContainerTransaction for transition - * @param onAnimationEndCallback to be called after animation - */ - public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct, - Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct, - onAnimationEndCallback); - } - - /** - * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - * @param wct WindowContainerTransaction for transition - * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move - * to desktop animation - * @param onAnimationEndCallback to be called after animation - */ - public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, - MoveToDesktopAnimator moveToDesktopAnimator, - Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - mMoveToDesktopAnimator = moveToDesktopAnimator; - startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct, - onAnimationEndCallback); - } - - /** * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP * @param wct WindowContainerTransaction for transition * @param decor {@link DesktopModeWindowDecoration} of task being animated @@ -139,8 +81,8 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public void moveToDesktop(@NonNull WindowContainerTransaction wct, DesktopModeWindowDecoration decor) { mDesktopModeWindowDecoration = decor; - startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct, - null /* onAnimationEndCallback */); + final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this); + mPendingTransitionTokens.add(token); } @Override @@ -182,30 +124,11 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP + if (type == TRANSIT_MOVE_TO_DESKTOP && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { return animateMoveToDesktop(change, startT, finishCallback); } - if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - return animateStartDragToDesktopMode(change, startT, finishT, finishCallback); - } - - final Rect endBounds = change.getEndAbsBounds(); - if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM - && !endBounds.isEmpty()) { - return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback, - endBounds); - } - - if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback, - endBounds); - } - return false; } @@ -248,142 +171,6 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition return true; } - private boolean animateStartDragToDesktopMode( - @NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startT, - @NonNull SurfaceControl.Transaction finishT, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - // Transitioning to freeform but keeping fullscreen bounds, so the crop is set - // to null and we don't require an animation - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, null); - - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; - } - - // Calculate and set position of the task - final PointF position = mMoveToDesktopAnimator.getPosition(); - startT.setPosition(sc, position.x, position.y); - finishT.setPosition(sc, position.x, position.y); - - startT.apply(); - - mTransitions.getMainExecutor().execute(() -> finishCallback.onTransitionFinished(null)); - - return true; - } - - private boolean animateFinalizeDragToDesktopMode( - @NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startT, - @NonNull SurfaceControl.Transaction finishT, - @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull Rect endBounds) { - // This Transition animates a task to freeform bounds after being dragged into freeform - // mode and brings the remaining freeform tasks to front - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, endBounds.width(), - endBounds.height()); - startT.apply(); - - // End the animation that shrinks the window when task is first dragged from fullscreen - if (mMoveToDesktopAnimator != null) { - mMoveToDesktopAnimator.endAnimator(); - } - - // We want to find the scale of the current bounds relative to the end bounds. The - // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be - // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to - // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds - final ValueAnimator animator = - ValueAnimator.ofFloat( - MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - animator.addUpdateListener(animation -> { - final float animationValue = (float) animation.getAnimatedValue(); - t.setScale(sc, animationValue, animationValue); - - final float animationWidth = endBounds.width() * animationValue; - final float animationHeight = endBounds.height() * animationValue; - final int animationX = endBounds.centerX() - (int) (animationWidth / 2); - final int animationY = endBounds.centerY() - (int) (animationHeight / 2); - - t.setPosition(sc, animationX, animationY); - t.apply(); - }); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null)); - } - }); - - animator.start(); - return true; - } - private boolean animateCancelDragToDesktopMode( - @NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startT, - @NonNull SurfaceControl.Transaction finishT, - @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull Rect endBounds) { - // This Transition animates a task to fullscreen after being dragged from the status - // bar and then released back into the status bar area - final SurfaceControl sc = change.getLeash(); - // Hide the first (fullscreen) frame because the animation will start from the smaller - // scale size. - startT.hide(sc) - .setWindowCrop(sc, endBounds.width(), endBounds.height()) - .apply(); - - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; - } - - // End the animation that shrinks the window when task is first dragged from fullscreen - mMoveToDesktopAnimator.endAnimator(); - - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - - // Get position of the task - final float x = mMoveToDesktopAnimator.getPosition().x; - final float y = mMoveToDesktopAnimator.getPosition().y; - - animator.addUpdateListener(animation -> { - final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, x * (1 - scale), y * (1 - scale)) - .setScale(sc, scale, scale) - .show(sc) - .apply(); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null)); - } - }); - animator.start(); - return true; - } - @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index ab5c0636f2b5..41ec33c9c762 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -150,19 +150,19 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition type for maximize to freeform transition. */ public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9; - /** Transition type for starting the move to desktop mode. */ - public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE = + /** Transition type for starting the drag to desktop mode. */ + public static final int TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 10; - /** Transition type for finalizing the move to desktop mode. */ - public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE = + /** Transition type for finalizing the drag to desktop mode. */ + public static final int TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 11; /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; - /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */ - public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE = + /** Transition type to cancel the drag to desktop mode. */ + public static final int TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 13; /** Transition type to animate the toggle resize between the max and default desktop sizes. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index e206039aa6bf..3add6f4175bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -268,13 +268,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { if (change.getMode() == WindowManager.TRANSIT_CHANGE - && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE - || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE - || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE + && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); + } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK + && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP + && change.getTaskInfo() != null) { + final DesktopModeWindowDecoration decor = + mWindowDecorByTaskId.get(change.getTaskInfo().taskId); + if (decor != null) { + decor.addTransitionPausingRelayout(transition); + } } } @@ -765,10 +771,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMoveToDesktopAnimator = null; return; } else if (mMoveToDesktopAnimator != null) { - relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator)); + c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo)); mMoveToDesktopAnimator = null; return; } @@ -790,15 +794,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { if (mMoveToDesktopAnimator == null) { - closeOtherSplitTask(relevantDecor.mTaskInfo.taskId); mMoveToDesktopAnimator = new MoveToDesktopAnimator( - mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, - relevantDecor.mTaskSurface); + mContext, mDragToDesktopAnimationStartBounds, + relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( - c -> c.startMoveToDesktop(relevantDecor.mTaskInfo, - mDragToDesktopAnimationStartBounds, - mMoveToDesktopAnimator)); - mMoveToDesktopAnimator.startAnimation(); + c -> { + final int taskId = relevantDecor.mTaskInfo.taskId; + relevantDecor.incrementRelayoutBlock(); + if (isTaskInSplitScreen(taskId)) { + final DesktopModeWindowDecoration otherDecor = + mWindowDecorByTaskId.get( + getOtherSplitTask(taskId).taskId); + if (otherDecor != null) { + otherDecor.incrementRelayoutBlock(); + } + } + c.startDragToDesktop(relevantDecor.mTaskInfo, + mMoveToDesktopAnimator, relevantDecor); + }); } } if (mMoveToDesktopAnimator != null) { @@ -837,7 +850,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void animateToDesktop(DesktopModeWindowDecoration relevantDecor, MotionEvent ev) { - relevantDecor.incrementRelayoutBlock(); centerAndMoveToDesktopWithAnimation(relevantDecor, ev); } @@ -853,15 +865,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final SurfaceControl sc = relevantDecor.mTaskSurface; final Rect endBounds = calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE); final Transaction t = mTransactionFactory.get(); - final float diffX = endBounds.centerX() - ev.getX(); - final float diffY = endBounds.top - ev.getY(); - final float startingX = ev.getX() - DRAG_FREEFORM_SCALE + final float diffX = endBounds.centerX() - ev.getRawX(); + final float diffY = endBounds.top - ev.getRawY(); + final float startingX = ev.getRawX() - DRAG_FREEFORM_SCALE * mDragToDesktopAnimationStartBounds.width() / 2; animator.addUpdateListener(animation -> { final float animatorValue = (float) animation.getAnimatedValue(); final float x = startingX + diffX * animatorValue; - final float y = ev.getY() + diffY * animatorValue; + final float y = ev.getRawY() + diffY * animatorValue; t.setPosition(sc, x, y); t.apply(); }); @@ -869,9 +881,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onAnimationEnd(Animator animation) { mDesktopTasksController.ifPresent( - c -> c.onDragPositioningEndThroughStatusBar( - relevantDecor.mTaskInfo, - calculateFreeformBounds(ev.getDisplayId(), FINAL_FREEFORM_SCALE))); + c -> { + c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo, + calculateFreeformBounds(ev.getDisplayId(), + FINAL_FREEFORM_SCALE)); + }); } }); animator.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt index b2267ddb6ba7..af055230b629 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -2,10 +2,12 @@ package com.android.wm.shell.windowdecor import android.animation.ValueAnimator import android.app.ActivityManager.RunningTaskInfo +import android.content.Context import android.graphics.PointF import android.graphics.Rect import android.view.MotionEvent import android.view.SurfaceControl +import com.android.internal.policy.ScreenDecorationsUtils /** * Creates an animator to shrink and position task after a user drags a fullscreen task from @@ -14,6 +16,7 @@ import android.view.SurfaceControl * accessed by the EnterDesktopTaskTransitionHandler. */ class MoveToDesktopAnimator @JvmOverloads constructor( + private val context: Context, private val startBounds: Rect, private val taskInfo: RunningTaskInfo, private val taskSurface: SurfaceControl, @@ -33,9 +36,11 @@ class MoveToDesktopAnimator @JvmOverloads constructor( .setDuration(ANIMATION_DURATION.toLong()) .apply { val t = SurfaceControl.Transaction() + val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) addUpdateListener { animation -> val animatorValue = animation.animatedValue as Float t.setScale(taskSurface, animatorValue, animatorValue) + .setCornerRadius(taskSurface, cornerRadius) .apply() } } @@ -44,19 +49,40 @@ class MoveToDesktopAnimator @JvmOverloads constructor( val position: PointF = PointF(0.0f, 0.0f) /** + * Whether motion events from the drag gesture should affect the dragged surface or not. Used + * to disallow moving the surface's position prematurely since it should not start moving at + * all until the drag-to-desktop transition is ready to animate and the wallpaper/home are + * ready to be revealed behind the dragged/scaled task. + */ + private var allowSurfaceChangesOnMove = false + + /** * Starts the animation that scales the task down. */ fun startAnimation() { + allowSurfaceChangesOnMove = true dragToDesktopAnimator.start() } /** - * Uses the position of the motion event and the current scale of the task as defined by the - * ValueAnimator to update the local position variable and set the task surface's position + * Uses the position of the motion event of the drag-to-desktop gesture to update the dragged + * task's position on screen to follow the touch point. Note that the position change won't + * be applied immediately always, such as near the beginning where it waits until the wallpaper + * or home are visible behind it. Once they're visible the surface will catch-up to the most + * recent touch position. */ fun updatePosition(ev: MotionEvent) { - position.x = ev.x - animatedTaskWidth / 2 - position.y = ev.y + // Using rawX/Y because when dragging a task in split, the local X/Y is relative to the + // split stages, but the split task surface is re-parented to the task display area to + // allow dragging beyond its stage across any region of the display. Because of that, the + // rawX/Y are more true to where the gesture is on screen and where the surface should be + // positioned. + position.x = ev.rawX - animatedTaskWidth / 2 + position.y = ev.rawY + + if (!allowSurfaceChangesOnMove) { + return + } val t = transactionFactory() t.setPosition(taskSurface, position.x, position.y) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index ebcb6407a6fd..fde6acb9bfe5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -100,6 +100,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler + @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration @Mock lateinit var splitScreenController: SplitScreenController @@ -127,7 +128,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } controller = createController() - controller.splitScreenController = splitScreenController + controller.setSplitScreenController(splitScreenController) shellInit.init() @@ -150,6 +151,7 @@ class DesktopTasksControllerTest : ShellTestCase() { enterDesktopTransitionHandler, exitDesktopTransitionHandler, mToggleResizeDesktopTaskTransitionHandler, + dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler, @@ -757,6 +759,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) + whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt new file mode 100644 index 000000000000..a5629c8f8f15 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -0,0 +1,211 @@ +package com.android.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.app.WindowConfiguration.WindowingMode +import android.graphics.PointF +import android.os.IBinder +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.TransitionInfo.FLAG_IS_WALLPAPER +import androidx.test.filters.SmallTest +import com.android.server.testutils.any +import com.android.server.testutils.mock +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.splitscreen.SplitScreenController +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator +import java.util.function.Supplier +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever + +/** Tests of [DragToDesktopTransitionHandler]. */ +@SmallTest +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DragToDesktopTransitionHandlerTest : ShellTestCase() { + + @Mock private lateinit var transitions: Transitions + @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var splitScreenController: SplitScreenController + + private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } + + private lateinit var handler: DragToDesktopTransitionHandler + + @Before + fun setUp() { + handler = + DragToDesktopTransitionHandler( + context, + transitions, + taskDisplayAreaOrganizer, + transactionSupplier + ) + .apply { setSplitScreenController(splitScreenController) } + } + + @Test + fun startDragToDesktop_animateDragWhenReady() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started. + val transition = startDragToDesktopTransition(task, dragAnimator) + + // Now it's ready to animate. + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + verify(dragAnimator).startAnimation() + } + + @Test + fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + + handler.cancelDragToDesktopTransition() + + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + // Don't even animate the "drag" since it was already cancelled. + verify(dragAnimator, never()).startAnimation() + // Instead, start the cancel transition. + verify(transitions) + .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler)) + } + + @Test + fun cancelDragToDesktop_startWasReady_cancel() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + whenever(dragAnimator.position).thenReturn(PointF()) + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + // Then user cancelled after it had already started. + handler.cancelDragToDesktopTransition() + + // Cancel animation should run since it had already started. + verify(dragAnimator).endAnimator() + } + + @Test + fun cancelDragToDesktop_startWasNotReady_animateCancel() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + // Simulate transition is started and is ready to animate. + startDragToDesktopTransition(task, dragAnimator) + + // Then user cancelled before the transition was ready and animated. + handler.cancelDragToDesktopTransition() + + // No need to animate the cancel since the start animation couldn't even start. + verifyZeroInteractions(dragAnimator) + } + + private fun startDragToDesktopTransition( + task: RunningTaskInfo, + dragAnimator: MoveToDesktopAnimator + ): IBinder { + val token = mock<IBinder>() + whenever( + transitions.startTransition( + eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP), + any(), + eq(handler) + ) + ) + .thenReturn(token) + handler.startDragToDesktopTransition(task.taskId, dragAnimator, mock()) + return token + } + + private fun createTask( + @WindowingMode windowingMode: Int = WINDOWING_MODE_FULLSCREEN, + isHome: Boolean = false, + ): RunningTaskInfo { + return TestRunningTaskInfoBuilder() + .setActivityType(if (isHome) ACTIVITY_TYPE_HOME else ACTIVITY_TYPE_STANDARD) + .setWindowingMode(windowingMode) + .build() + .also { + whenever(splitScreenController.isTaskInSplitScreen(it.taskId)) + .thenReturn(windowingMode == WINDOWING_MODE_MULTI_WINDOW) + } + } + + private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo { + return TransitionInfo(type, 0 /* flags */).apply { + addChange( // Home. + TransitionInfo.Change(mock(), mock()).apply { + parent = null + taskInfo = + TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build() + flags = flags or FLAG_IS_WALLPAPER + } + ) + addChange( // Dragged Task. + TransitionInfo.Change(mock(), mock()).apply { + parent = null + taskInfo = draggedTask + } + ) + addChange( // Wallpaper. + TransitionInfo.Change(mock(), mock()).apply { + parent = null + taskInfo = null + flags = flags or FLAG_IS_WALLPAPER + } + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java deleted file mode 100644 index 772d97d8eb32..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.wm.shell.desktopmode; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - -import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.app.WindowConfiguration; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.IBinder; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.IWindowContainerToken; -import android.window.TransitionInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; - -import junit.framework.AssertionFailedError; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.function.Supplier; - -/** Tests of {@link com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler} */ -@SmallTest -public class EnterDesktopTaskTransitionHandlerTest { - - @Mock - private Transitions mTransitions; - @Mock - IBinder mToken; - @Mock - Supplier<SurfaceControl.Transaction> mTransactionFactory; - @Mock - SurfaceControl.Transaction mStartT; - @Mock - SurfaceControl.Transaction mFinishT; - @Mock - SurfaceControl.Transaction mAnimationT; - @Mock - Transitions.TransitionFinishCallback mTransitionFinishCallback; - @Mock - ShellExecutor mExecutor; - @Mock - SurfaceControl mSurfaceControl; - @Mock - MoveToDesktopAnimator mMoveToDesktopAnimator; - @Mock - PointF mPosition; - - private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - doReturn(mExecutor).when(mTransitions).getMainExecutor(); - doReturn(mAnimationT).when(mTransactionFactory).get(); - doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition(); - - mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions, - mTransactionFactory); - } - - @Test - public void testEnterFreeformAnimation() { - final int taskId = 1; - WindowContainerTransaction wct = new WindowContainerTransaction(); - doReturn(mToken).when(mTransitions) - .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct, - mEnterDesktopTaskTransitionHandler); - doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId(); - - mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct, - mMoveToDesktopAnimator, null); - - TransitionInfo.Change change = - createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); - TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, - change); - - - assertTrue(mEnterDesktopTaskTransitionHandler - .startAnimation(mToken, info, mStartT, mFinishT, mTransitionFinishCallback)); - - verify(mStartT).setWindowCrop(mSurfaceControl, null); - verify(mStartT).apply(); - } - - @Test - public void testTransitEnterDesktopModeAnimation() throws Throwable { - final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE; - final int taskId = 1; - WindowContainerTransaction wct = new WindowContainerTransaction(); - doReturn(mToken).when(mTransitions) - .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null); - - TransitionInfo.Change change = - createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); - change.setEndAbsBounds(new Rect(0, 0, 1, 1)); - TransitionInfo info = createTransitionInfo( - Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change); - - runOnUiThread(() -> { - try { - assertTrue(mEnterDesktopTaskTransitionHandler - .startAnimation(mToken, info, mStartT, mFinishT, - mTransitionFinishCallback)); - } catch (Exception e) { - throw new AssertionFailedError(e.getMessage()); - } - }); - - verify(mStartT).setWindowCrop(mSurfaceControl, change.getEndAbsBounds().width(), - change.getEndAbsBounds().height()); - verify(mStartT).apply(); - } - - private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId, - @WindowConfiguration.WindowingMode int windowingMode) { - final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); - taskInfo.taskId = taskId; - taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); - final TransitionInfo.Change change = new TransitionInfo.Change( - new WindowContainerToken(mock(IWindowContainerToken.class)), mSurfaceControl); - change.setMode(type); - change.setTaskInfo(taskInfo); - return change; - } - - private static TransitionInfo createTransitionInfo( - @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) { - TransitionInfo info = new TransitionInfo(type, 0); - info.addChange(change); - return info; - } - -} diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index feb914fe3161..757e9f8e41b1 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -630,7 +630,6 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>(); ScopedLocalRef<jobject> audioDescriptor(env); - gAudioPresentationFields.init(env); ScopedLocalRef presentationsJObj(env, JAudioPresentationInfo::asJobject( env, gAudioPresentationFields)); switch (mediaEvent.extraMetaData.getTag()) { @@ -3731,6 +3730,7 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { gFields.linearBlockInitID = env->GetMethodID(linearBlockClazz, "<init>", "()V"); gFields.linearBlockSetInternalStateID = env->GetMethodID(linearBlockClazz, "setInternalStateLocked", "(JZ)V"); + gAudioPresentationFields.init(env); } static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) { diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index fc4ed1d4d527..b9e34ee97f21 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -33,3 +33,10 @@ java_library { ], visibility: ["//visibility:public"], } + +java_host_for_device { + name: "core-xml-for-device", + libs: [ + "core-xml-for-host", + ], +} diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index 692d598ac2bb..3e54c7a06288 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -103,7 +103,21 @@ class android.os.TransactionTooLargeException stubclass # Containers class android.os.BaseBundle stubclass class android.os.Bundle stubclass +class android.os.PersistableBundle stubclass # Misc class android.os.PatternMatcher stubclass class android.os.ParcelUuid stubclass + +# XML +class com.android.internal.util.XmlPullParserWrapper stubclass +class com.android.internal.util.XmlSerializerWrapper stubclass +class com.android.internal.util.XmlUtils stubclass + +class com.android.modules.utils.BinaryXmlPullParser stubclass +class com.android.modules.utils.BinaryXmlSerializer stubclass +class com.android.modules.utils.FastDataInput stubclass +class com.android.modules.utils.FastDataOutput stubclass +class com.android.modules.utils.ModifiedUtf8 stubclass +class com.android.modules.utils.TypedXmlPullParser stubclass +class com.android.modules.utils.TypedXmlSerializer stubclass diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 776a19a68a31..1ac6bf0a7c4d 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -2,8 +2,11 @@ com.android.internal.util.ArrayUtils +android.util.Xml + android.os.Binder android.os.Binder$IdentitySupplier android.os.IBinder android.os.Process android.os.SystemClock +android.os.UserHandle diff --git a/services/backup/Android.bp b/services/backup/Android.bp index b086406a2ad5..acb5911c8868 100644 --- a/services/backup/Android.bp +++ b/services/backup/Android.bp @@ -19,5 +19,16 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.backup-sources"], libs: ["services.core"], - static_libs: ["app-compat-annotations"], + static_libs: ["app-compat-annotations", "backup_flags_lib"], +} + +aconfig_declarations { + name: "backup_flags", + package: "com.android.server.backup", + srcs: ["flags.aconfig"], +} + +java_aconfig_library { + name: "backup_flags_lib", + aconfig_declarations: "backup_flags", } diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig new file mode 100644 index 000000000000..d695d36db0ea --- /dev/null +++ b/services/backup/flags.aconfig @@ -0,0 +1,10 @@ +package: "com.android.server.backup" + +flag { + name: "enable_skipping_restore_launched_apps" + namespace: "onboarding" + description: "Enforce behavior determined by BackupTransport implementation on whether to skip " + "restore for apps that have been launched." + bug: "308401499" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index 70d7fac09a4f..9f7b62763ed3 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -24,6 +24,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; @@ -32,11 +33,15 @@ import android.app.backup.RestoreSet; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Handler; import android.os.Message; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.backup.Flags; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; @@ -296,12 +301,26 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { return -1; } - private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) { + @VisibleForTesting + BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) { // TODO(b/182986784): Remove device name comparison once a designated field for operation // type is added to RestoreSet object. int backupDestination = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device) ? BackupDestination.DEVICE_TRANSFER : BackupDestination.CLOUD; - return mBackupManagerService.getEligibilityRulesForOperation(backupDestination); + + if (!Flags.enableSkippingRestoreLaunchedApps()) { + return mBackupManagerService.getEligibilityRulesForOperation(backupDestination); + } + + boolean skipRestoreForLaunchedApps = (restoreSet.backupTransportFlags + & BackupAgent.FLAG_SKIP_RESTORE_FOR_LAUNCHED_APPS) != 0; + + return new BackupEligibilityRules(mBackupManagerService.getPackageManager(), + LocalServices.getService(PackageManagerInternal.class), + mUserId, + mBackupManagerService.getContext(), + backupDestination, + skipRestoreForLaunchedApps); } public synchronized int restorePackage(String packageName, IRestoreObserver observer, diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index bbec79d6cd47..96a873ecc217 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -63,6 +63,7 @@ import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupAndRestoreFeatureFlags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.BackupUtils; +import com.android.server.backup.Flags; import com.android.server.backup.OperationStorage; import com.android.server.backup.OperationStorage.OpType; import com.android.server.backup.PackageManagerBackupAgent; @@ -263,7 +264,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { continue; } - if (backupEligibilityRules.appIsEligibleForBackup(info.applicationInfo)) { + + ApplicationInfo applicationInfo = info.applicationInfo; + if (backupEligibilityRules.appIsEligibleForBackup(applicationInfo)) { + if (Flags.enableSkippingRestoreLaunchedApps() + && !backupEligibilityRules.isAppEligibleForRestore(applicationInfo)) { + continue; + } + mAcceptSet.add(info); } } catch (NameNotFoundException e) { diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java index 7c47f1e477b6..f24a3c1afc86 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java +++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java @@ -80,6 +80,7 @@ public class BackupEligibilityRules { private final int mUserId; private boolean mIsProfileUser = false; @BackupDestination private final int mBackupDestination; + private final boolean mSkipRestoreForLaunchedApps; /** * When this change is enabled, {@code adb backup} is automatically turned on for apps @@ -112,12 +113,23 @@ public class BackupEligibilityRules { int userId, Context context, @BackupDestination int backupDestination) { + this(packageManager, packageManagerInternal, userId, context, backupDestination, + /* skipRestoreForLaunchedApps */ false); + } + + public BackupEligibilityRules(PackageManager packageManager, + PackageManagerInternal packageManagerInternal, + int userId, + Context context, + @BackupDestination int backupDestination, + boolean skipRestoreForLaunchedApps) { mPackageManager = packageManager; mPackageManagerInternal = packageManagerInternal; mUserId = userId; mBackupDestination = backupDestination; UserManager userManager = context.getSystemService(UserManager.class); mIsProfileUser = userManager.isProfile(); + mSkipRestoreForLaunchedApps = skipRestoreForLaunchedApps; } /** @@ -132,6 +144,9 @@ public class BackupEligibilityRules { * <li>it is the special shared-storage backup package used for 'adb backup' * </ol> * + * These eligibility conditions are also checked before restore, in case the backup happened on + * a device / from the version of the app where these rules were not enforced. + * * However, the above eligibility rules are ignored for non-system apps in in case of * device-to-device migration, see {@link BackupDestination}. */ @@ -283,6 +298,27 @@ public class BackupEligibilityRules { } } + /** + * Determine if data restore should be run for the given package. + * + * <p>This is used in combination with {@link #appIsEligibleForBackup(ApplicationInfo)} that + * checks whether the backup being restored should have happened in the first place.</p> + */ + public boolean isAppEligibleForRestore(ApplicationInfo app) { + if (!mSkipRestoreForLaunchedApps) { + return true; + } + + // If an app implemented a BackupAgent, they are expected to handle being restored even + // after first launch and avoid conflicts between existing app data and restored data. + if (app.backupAgentName != null) { + return true; + } + + // Otherwise only restore an app if it hasn't been launched before. + return !mPackageManagerInternal.wasPackageEverLaunched(app.packageName, mUserId); + } + /** Avoid backups of 'disabled' apps. */ @VisibleForTesting boolean appIsDisabled( diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index f9fc4d4f27fa..36356bd95128 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -111,6 +111,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ParseUtils; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.server.LocalServices; import com.android.server.Watchdog; @@ -475,7 +476,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); try { - nms.registerObserver(mActivityChangeObserver); + if (!SdkLevel.isAtLeastV()) { + // On V+ devices, ConnectivityService calls BatteryStats API to update + // RadioPowerState change. So BatteryStatsService registers the callback only on + // pre V devices. + nms.registerObserver(mActivityChangeObserver); + } cm.registerDefaultNetworkCallback(mNetworkCallback); } catch (RemoteException e) { Slog.e(TAG, "Could not register INetworkManagement event observer " + e); diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index 392b86e2df1f..681d1a0ee10a 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -327,10 +327,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub { /** * Notify our observers of a change in the data activity state of the interface */ - private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos, + private void notifyInterfaceClassActivity(int label, boolean isActive, long tsNanos, int uid) { invokeForAllObservers(o -> o.interfaceClassDataActivityChanged( - type, isActive, tsNanos, uid)); + label, isActive, tsNanos, uid)); } // Sync the state of the given chain with the native daemon. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bae06347d8a2..7e5152669837 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -706,6 +706,7 @@ public class NotificationManagerService extends SystemService { private boolean mNotificationEffectsEnabledForAutomotive; private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener; protected NotificationAttentionHelper mAttentionHelper; + private boolean mFlagRefactorAttentionHelper; private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; @@ -1189,7 +1190,7 @@ public class NotificationManagerService extends SystemService { @Override public void onSetDisabled(int status) { synchronized (mNotificationLock) { - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.updateDisableNotificationEffectsLocked(status); } else { mDisableNotificationEffects = @@ -1335,7 +1336,7 @@ public class NotificationManagerService extends SystemService { public void clearEffects() { synchronized (mNotificationLock) { if (DBG) Slog.d(TAG, "clearEffects"); - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.clearAttentionEffects(); } else { clearSoundLocked(); @@ -1564,7 +1565,7 @@ public class NotificationManagerService extends SystemService { int changedFlags = data.getFlags() ^ flags; if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) { // Suppress notification flag changed, clear any effects - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.clearEffectsLocked(key); } else { clearEffectsLocked(key); @@ -1913,7 +1914,7 @@ public class NotificationManagerService extends SystemService { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (!Flags.refactorAttentionHelper()) { + if (!mFlagRefactorAttentionHelper) { if (action.equals(Intent.ACTION_SCREEN_ON)) { // Keep track of screen on/off state, but do not turn off the notification light // until user passes through the lock screen or views the notification. @@ -2029,7 +2030,7 @@ public class NotificationManagerService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(NOTIFICATION_BADGING_URI, false, this, UserHandle.USER_ALL); - if (!Flags.refactorAttentionHelper()) { + if (!mFlagRefactorAttentionHelper) { resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this, UserHandle.USER_ALL); } @@ -2059,7 +2060,7 @@ public class NotificationManagerService extends SystemService { public void update(Uri uri) { ContentResolver resolver = getContext().getContentResolver(); - if (!Flags.refactorAttentionHelper()) { + if (!mFlagRefactorAttentionHelper) { if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { boolean pulseEnabled = Settings.System.getIntForUser(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) @@ -2560,7 +2561,9 @@ public class NotificationManagerService extends SystemService { mToastRateLimiter = toastRateLimiter; - if (Flags.refactorAttentionHelper()) { + //Cache aconfig flag value + mFlagRefactorAttentionHelper = Flags.refactorAttentionHelper(); + if (mFlagRefactorAttentionHelper) { mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager, mAccessibilityManager, mPackageManagerClient, userManager, usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver); @@ -2570,7 +2573,7 @@ public class NotificationManagerService extends SystemService { // If this is called within a test, make sure to unregister the intent receivers by // calling onDestroy() IntentFilter filter = new IntentFilter(); - if (!Flags.refactorAttentionHelper()) { + if (!mFlagRefactorAttentionHelper) { filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -2898,7 +2901,7 @@ public class NotificationManagerService extends SystemService { } registerNotificationPreferencesPullers(); new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker); - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.onSystemReady(); } } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { @@ -6571,7 +6574,7 @@ public class NotificationManagerService extends SystemService { pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate); pw.println(" hideSilentStatusBar=" + mPreferencesHelper.shouldHideSilentStatusIcons()); - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.dump(pw, " ", filter); } } @@ -7872,7 +7875,7 @@ public class NotificationManagerService extends SystemService { boolean wasPosted = removeFromNotificationListsLocked(r); cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null, SystemClock.elapsedRealtime()); - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); @@ -8012,7 +8015,7 @@ public class NotificationManagerService extends SystemService { cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, mSendDelete, childrenFlagChecker, mReason, mCancellationElapsedTimeMs); - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); @@ -8309,7 +8312,7 @@ public class NotificationManagerService extends SystemService { int buzzBeepBlinkLoggingCode = 0; if (!r.isHidden()) { - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals( mUserProfiles.isCurrentProfile(r.getUserId()), @@ -9296,7 +9299,7 @@ public class NotificationManagerService extends SystemService { || interruptiveChanged; if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.buzzBeepBlinkLocked(record, new NotificationAttentionHelper.Signals( mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints)); @@ -9676,7 +9679,7 @@ public class NotificationManagerService extends SystemService { }); } - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.clearEffectsLocked(canceledKey); } else { // sound @@ -10040,7 +10043,7 @@ public class NotificationManagerService extends SystemService { cancellationElapsedTimeMs); } } - if (Flags.refactorAttentionHelper()) { + if (mFlagRefactorAttentionHelper) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index c8fd16dbd5dc..997b6084f6e2 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -393,8 +393,12 @@ final class InputMonitor { */ void setActiveRecents(@Nullable ActivityRecord activity, @Nullable ActivityRecord layer) { final boolean clear = activity == null; + final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null; mActiveRecentsActivity = clear ? null : new WeakReference<>(activity); mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer); + if (clear && wasActive) { + setUpdateInputWindowsNeededLw(); + } } private static <T> T getWeak(WeakReference<T> ref) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f70094450abb..caa57bb032ca 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1344,6 +1344,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); + dc.getInputMonitor().updateInputWindowsLw(false /* force */); } if (mTransientLaunches != null) { for (int i = mTransientLaunches.size() - 1; i >= 0; --i) { diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 063af573e1f3..113511ef8197 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -46,6 +46,7 @@ android_test { "androidx.test.espresso.core", "androidx.test.espresso.contrib", "androidx.test.ext.truth", + "backup_flags_lib", "flag-junit", "frameworks-base-testutils", "hamcrest-library", diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java new file mode 100644 index 000000000000..0006d0ac92aa --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -0,0 +1,134 @@ +/* + * 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.server.backup.restore; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.backup.BackupAgent; +import android.app.backup.RestoreSet; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.os.UserManager; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.LocalServices; +import com.android.server.backup.Flags; +import com.android.server.backup.TransportManager; +import com.android.server.backup.UserBackupManagerService; +import com.android.server.backup.utils.BackupEligibilityRules; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActiveRestoreSessionTest { + private static final String TEST_APP_NAME = "test_app"; + + private ActiveRestoreSession mRestoreSession; + private ApplicationInfo mTestApp; + + @Mock + private UserBackupManagerService mBackupManagerService; + @Mock + private BackupEligibilityRules mBackupEligibilityRules; + @Mock + private Context mContext; + @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock + private TransportManager mTransportManager; + @Mock private UserManager mUserManager; + + @Rule + public final SetFlagsRule mFlagsRule = new SetFlagsRule(); + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule + .Builder(/* testClassInstance */ this) + .mockStatic(LocalServices.class) + .afterSessionFinished( + () -> LocalServices.removeServiceForTest(PackageManagerInternal.class) + ).build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(/* testClass */ this); + when(mBackupEligibilityRules.isAppEligibleForRestore(any())).thenReturn(true); + when(mBackupManagerService.getEligibilityRulesForOperation(anyInt())).thenReturn( + mBackupEligibilityRules); + when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager); + when(mBackupManagerService.getContext()).thenReturn(mContext); + when(mContext.getSystemService(eq(UserManager.class))).thenReturn(mUserManager); + when(LocalServices.getService(PackageManagerInternal.class)).thenReturn( + mPackageManagerInternal); + + mRestoreSession = new ActiveRestoreSession(mBackupManagerService, + /* packageName */ null, + /* transportName */ "", + mBackupEligibilityRules); + mTestApp = new ApplicationInfo(); + mTestApp.packageName = TEST_APP_NAME; + } + + @Test + public void testGetBackupEligibilityRules_skipRestoreFlagOn_skipsLaunchedAppRestore() { + mFlagsRule.enableFlags(Flags.FLAG_ENABLE_SKIPPING_RESTORE_LAUNCHED_APPS); + RestoreSet restoreSet = new RestoreSet( + /* name */ null, + /* device */ null, + /* token */ 0, + /* backupTransportFlags */ BackupAgent.FLAG_SKIP_RESTORE_FOR_LAUNCHED_APPS); + when(mPackageManagerInternal.wasPackageEverLaunched(eq(TEST_APP_NAME), anyInt())) + .thenReturn(true); + + BackupEligibilityRules eligibilityRules = mRestoreSession.getBackupEligibilityRules( + restoreSet); + + assertThat(eligibilityRules.isAppEligibleForRestore(mTestApp)).isFalse(); + } + + @Test + public void testGetBackupEligibilityRules_skipRestoreFlagOff_allowsAppRestore() { + mFlagsRule.disableFlags(Flags.FLAG_ENABLE_SKIPPING_RESTORE_LAUNCHED_APPS); + RestoreSet restoreSet = new RestoreSet( + /* name */ null, + /* device */ null, + /* token */ 0, + /* backupTransportFlags */ BackupAgent.FLAG_SKIP_RESTORE_FOR_LAUNCHED_APPS); + when(mPackageManagerInternal.wasPackageEverLaunched(eq(TEST_APP_NAME), anyInt())) + .thenReturn(true); + + BackupEligibilityRules eligibilityRules = mRestoreSession.getBackupEligibilityRules( + restoreSet); + + assertThat(eligibilityRules.isAppEligibleForRestore(mTestApp)).isTrue(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java index 030665537c38..290b26027340 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupEligibilityRulesTest.java @@ -860,6 +860,66 @@ public class BackupEligibilityRulesTest { assertThat(result).isFalse(); } + @Test + public void isAppEligibleForRestore_hasBeenLaunched_returnsFalse() { + when(mMockPackageManagerInternal.wasPackageEverLaunched(eq(TEST_PACKAGE_NAME), eq(mUserId))) + .thenReturn(true); + ApplicationInfo applicationInfo = getApplicationInfo(/* appUid */ 0, /* flags */ 0, + /* backupAgentName */ null); + BackupEligibilityRules backupEligibilityRules = new BackupEligibilityRules(mPackageManager, + mMockPackageManagerInternal, mUserId, mContext, BackupDestination.CLOUD, + /* skipRestoreForLaunchedApps */ true); + + boolean isEligible = backupEligibilityRules.isAppEligibleForRestore(applicationInfo); + + assertThat(isEligible).isFalse(); + } + + @Test + public void isAppEligibleForRestore_hasNotBeenLaunched_returnsTrue() { + when(mMockPackageManagerInternal.wasPackageEverLaunched(eq(TEST_PACKAGE_NAME), eq(mUserId))) + .thenReturn(false); + ApplicationInfo applicationInfo = getApplicationInfo(/* appUid */ 0, /* flags */ 0, + /* backupAgentName */ null); + BackupEligibilityRules backupEligibilityRules = new BackupEligibilityRules(mPackageManager, + mMockPackageManagerInternal, mUserId, mContext, BackupDestination.CLOUD, + /* skipRestoreForLaunchedApps */ false); + + boolean isEligible = backupEligibilityRules.isAppEligibleForRestore(applicationInfo); + + assertThat(isEligible).isTrue(); + } + + @Test + public void isAppEligibleForRestore_launchedButHasBackupAgent_returnsTrue() { + when(mMockPackageManagerInternal.wasPackageEverLaunched(eq(TEST_PACKAGE_NAME), eq(mUserId))) + .thenReturn(true); + ApplicationInfo applicationInfo = getApplicationInfo(/* appUid */ 0, /* flags */ 0, + /* backupAgentName */ "BackupAgent"); + BackupEligibilityRules backupEligibilityRules = new BackupEligibilityRules(mPackageManager, + mMockPackageManagerInternal, mUserId, mContext, BackupDestination.CLOUD, + /* skipRestoreForLaunchedApps */ false); + + boolean isEligible = backupEligibilityRules.isAppEligibleForRestore(applicationInfo); + + assertThat(isEligible).isTrue(); + } + + @Test + public void isAppEligibleForRestore_doNotSkipRestoreForLaunched_returnsTrue() { + when(mMockPackageManagerInternal.wasPackageEverLaunched(eq(TEST_PACKAGE_NAME), eq(mUserId))) + .thenReturn(true); + ApplicationInfo applicationInfo = getApplicationInfo(/* appUid */ 0, /* flags */ 0, + /* backupAgentName */ null); + BackupEligibilityRules backupEligibilityRules = new BackupEligibilityRules(mPackageManager, + mMockPackageManagerInternal, mUserId, mContext, BackupDestination.CLOUD, + /* skipRestoreForLaunchedApps */ false); + + boolean isEligible = backupEligibilityRules.isAppEligibleForRestore(applicationInfo); + + assertThat(isEligible).isTrue(); + } + private BackupEligibilityRules getBackupEligibilityRules( @BackupDestination int backupDestination) { return new BackupEligibilityRules(mPackageManager, mMockPackageManagerInternal, mUserId, diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index fd4ec8bd5931..5949bca2f9a3 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -284,6 +284,9 @@ java_library { "hoststubgen-helper-runtime.ravenwood", "framework-minus-apex.ravenwood", ], + static_libs: [ + "core-xml-for-device", + ], } // Defaults for host side test modules. |