Merge "Shrink default launch bounds on small display" into tm-qpr-dev
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 97ec594..9bad0de 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -47,6 +47,14 @@
public abstract int getBatteryLevel();
/**
+ * Returns battery health status as an integer representing the current battery health constant.
+ *
+ * This is a simple accessor that's safe to be called from any locks, but internally it may
+ * wait on the battery service lock.
+ */
+ public abstract int getBatteryHealth();
+
+ /**
* Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
* Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
*
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 3250dd8..d25c8a8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,13 +39,13 @@
* animations if the transition only contains windows that belong to the organized
* TaskFragments in the given Task.
*/
- void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
+ void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
in RemoteAnimationDefinition definition);
/**
* Unregisters remote animations per transition type for the organizer.
*/
- void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
+ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
/**
* Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 648541b..ab7d616 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -140,16 +140,13 @@
/**
* Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized
- * TaskFragments in the given Task.
- *
- * @param taskId overrides if the transition only contains windows belonging to this Task.
+ * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
* @hide
*/
@CallSuper
- public void registerRemoteAnimations(int taskId,
- @NonNull RemoteAnimationDefinition definition) {
+ public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
try {
- getController().registerRemoteAnimations(mInterface, taskId, definition);
+ getController().registerRemoteAnimations(mInterface, definition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -160,9 +157,9 @@
* @hide
*/
@CallSuper
- public void unregisterRemoteAnimations(int taskId) {
+ public void unregisterRemoteAnimations() {
try {
- getController().unregisterRemoteAnimations(mInterface, taskId);
+ getController().unregisterRemoteAnimations(mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index bd4f990..004d5d6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -189,6 +189,8 @@
optional bool is_enhanced_discharge_prediction_personalized = 54;
optional bool is_low_power_standby_active = 55;
optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
+ // The battery level drained by the dream.
+ optional int32 battery_level_drained_while_dreaming = 57;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9d841ea..d7d43aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -37,7 +37,6 @@
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -85,26 +84,20 @@
@Override
public void unregisterOrganizer() {
if (mAnimationController != null) {
- mAnimationController.unregisterAllRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer();
}
- /** Overrides the animation if the transition is on the given Task. */
- void startOverrideSplitAnimation(int taskId) {
+ /**
+ * Overrides the animation for transitions of embedded activities organized by this organizer.
+ */
+ void overrideSplitAnimation() {
if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this);
}
- mAnimationController.registerRemoteAnimations(taskId);
- }
-
- /** No longer overrides the animation if the transition is on the given Task. */
- @GuardedBy("mLock")
- void stopOverrideSplitAnimation(int taskId) {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations(taskId);
- }
+ mAnimationController.registerRemoteAnimations();
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index d52caaf..c06548a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -193,7 +193,6 @@
continue;
}
updateContainersInTask(wct, taskContainer);
- updateAnimationOverride(taskContainer);
}
// The WCT should be applied and merged to the device state change transition if
// there is one.
@@ -208,9 +207,6 @@
synchronized (mLock) {
mSplitRules.clear();
mSplitRules.addAll(rules);
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- updateAnimationOverride(mTaskContainers.valueAt(i));
- }
}
}
@@ -612,7 +608,6 @@
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
mTaskContainers.remove(taskContainer.getTaskId());
}
return;
@@ -622,43 +617,7 @@
@GuardedBy("mLock")
private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
@NonNull Configuration config) {
- final boolean wasInPip = taskContainer.isInPictureInPicture();
- final boolean isInPIp = isInPictureInPicture(config);
-
- // We need to check the animation override when enter/exit PIP or has bounds changed.
- boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
- if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
- && !isInPIp) {
- // We don't care the bounds change when it has already entered PIP.
- shouldUpdateAnimationOverride = true;
- }
- if (shouldUpdateAnimationOverride) {
- updateAnimationOverride(taskContainer);
- }
- }
-
- /**
- * Updates if we should override transition animation. We only want to override if the Task
- * bounds is large enough for at least one split rule.
- */
- @GuardedBy("mLock")
- private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
- if (ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell with Shell transition enabled.
- return;
- }
- if (!taskContainer.isTaskBoundsInitialized()) {
- // We don't know about the Task bounds/windowingMode yet.
- return;
- }
-
- // We only want to override if the TaskContainer may show split.
- if (mayShowSplit(taskContainer)) {
- mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
- } else {
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
- }
+ taskContainer.setTaskBounds(config.windowConfiguration.getBounds());
}
/** Returns whether the given {@link TaskContainer} may show in split. */
@@ -1283,7 +1242,6 @@
Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
}
}
- updateAnimationOverride(taskContainer);
return container;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index cb470ba..f494b32 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -139,6 +139,11 @@
super(executor, controller);
mController = controller;
registerOrganizer();
+ if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell when Shell transition is enabled.
+ overrideSplitAnimation();
+ }
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139..d7eb9a0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import android.util.ArraySet;
import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@
private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
@VisibleForTesting
final RemoteAnimationDefinition mDefinition;
- /** Task Ids that we have registered for remote animation. */
- private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+ private boolean mIsRegistered;
TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
@@ -54,39 +50,30 @@
new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
}
- void registerRemoteAnimations(int taskId) {
+ void registerRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "registerRemoteAnimations");
}
- if (mRegisterTasks.contains(taskId)) {
+ if (mIsRegistered) {
return;
}
- mOrganizer.registerRemoteAnimations(taskId, mDefinition);
- mRegisterTasks.add(taskId);
+ mOrganizer.registerRemoteAnimations(mDefinition);
+ mIsRegistered = true;
}
- void unregisterRemoteAnimations(int taskId) {
+ void unregisterRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "unregisterRemoteAnimations");
}
- if (!mRegisterTasks.contains(taskId)) {
+ if (!mIsRegistered) {
return;
}
- mOrganizer.unregisterRemoteAnimations(taskId);
- mRegisterTasks.remove(taskId);
- }
-
- void unregisterAllRemoteAnimations() {
- final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
- for (int taskId : tasks) {
- unregisterRemoteAnimations(taskId);
- }
+ mOrganizer.unregisterRemoteAnimations();
+ mIsRegistered = false;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e8..0e13c59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -20,11 +20,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import android.animation.Animator;
@@ -169,11 +167,9 @@
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case TRANSIT_OLD_TASK_OPEN:
return createOpenAnimationAdapters(targets);
case TRANSIT_OLD_ACTIVITY_CLOSE:
case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- case TRANSIT_OLD_TASK_CLOSE:
return createCloseAnimationAdapters(targets);
case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
return createChangeAnimationAdapters(targets);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 79813c7..31aa09c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,7 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
@@ -85,35 +82,20 @@
@Test
public void testUnregisterOrganizer() {
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+ mOrganizer.overrideSplitAnimation();
mOrganizer.unregisterOrganizer();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
@Test
- public void testStartOverrideSplitAnimation() {
+ public void testOverrideSplitAnimation() {
assertNull(mOrganizer.mAnimationController);
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
+ mOrganizer.overrideSplitAnimation();
assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(TASK_ID,
- mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
- public void testStopOverrideSplitAnimation() {
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342b..379ea0c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
package androidx.window.extensions.embedding;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@
@Test
public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
// No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
}
@Test
public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+ verify(mOrganizer, never()).unregisterRemoteAnimations();
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).unregisterRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- }
-
- @Test
- public void testUnregisterAllRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.registerRemoteAnimations(TASK_ID + 1);
- mAnimationController.unregisterAllRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 780ee5f..7558b41 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -75,7 +75,11 @@
@JvmField
val NOTIFICATION_DISMISSAL_FADE =
unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
- val STABILITY_INDEX_FIX = unreleasedFlag(114, "stability_index_fix", teamfood = true)
+
+ // TODO(b/259558771): Tracking Bug
+ val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
+
+ // TODO(b/259559750): Tracking Bug
val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b719177..8698c04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -246,7 +246,6 @@
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.setFitInsetsTypes(0 /* types */);
- mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("NotificationShade");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -384,8 +383,6 @@
mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
-
- mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
private void applyForceShowNavigationFlag(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 585d871..37d82ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -676,9 +676,6 @@
return row != null && row.areChildrenExpanded();
}
- public boolean keepInParent() {
- return row != null && row.keepInParent();
- }
//TODO: probably less confusing to say "is group fully visible"
public boolean isGroupNotFullyVisible() {
@@ -698,10 +695,6 @@
return row != null && row.isSummaryWithChildren();
}
- public void setKeepInParent(boolean keep) {
- if (row != null) row.setKeepInParent(keep);
- }
-
public void onDensityOrFontScaleChanged() {
if (row != null) row.onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3ae2545..65a21a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1160,12 +1160,21 @@
mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
}
- if (curr.getSuppressedChanges().getParent() != null) {
- mLogger.logParentChangeSuppressed(
+ GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+ GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+ if (currSuppressedParent != null && (prevSuppressedParent == null
+ || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
+ mLogger.logParentChangeSuppressedStarted(
mIterationCount,
- curr.getSuppressedChanges().getParent(),
+ currSuppressedParent,
curr.getParent());
}
+ if (prevSuppressedParent != null && currSuppressedParent == null) {
+ mLogger.logParentChangeSuppressedStopped(
+ mIterationCount,
+ prevSuppressedParent,
+ prev.getParent());
+ }
if (curr.getSuppressedChanges().getSection() != null) {
mLogger.logSectionChangeSuppressed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8e052c7..4adc90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -193,7 +193,7 @@
})
}
- fun logParentChangeSuppressed(
+ fun logParentChangeSuppressedStarted(
buildId: Int,
suppressedParent: GroupEntry?,
keepingParent: GroupEntry?
@@ -207,6 +207,21 @@
})
}
+ fun logParentChangeSuppressedStopped(
+ buildId: Int,
+ previouslySuppressedParent: GroupEntry?,
+ previouslyKeptParent: GroupEntry?
+ ) {
+ buffer.log(TAG, INFO, {
+ long1 = buildId.toLong()
+ str1 = previouslySuppressedParent?.logKey
+ str2 = previouslyKeptParent?.logKey
+ }, {
+ "(Build $long1) Change of parent to '$str1' no longer suppressed; " +
+ "replaced parent '$str2'"
+ })
+ }
+
fun logGroupPruningSuppressed(
buildId: Int,
keepingParent: GroupEntry?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index f949af0..8de0381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -55,4 +55,8 @@
override val view: View
get() = mediaContainerView!!
+
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 26ba12c..ae72a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -30,6 +30,7 @@
* below.
*/
interface NodeController {
+
/** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
val nodeLabel: String
@@ -64,6 +65,27 @@
/** Called when this view has been removed */
fun onViewRemoved() {}
+
+ /**
+ * Called before removing a node from its parent
+ *
+ * If returned true, the ShadeViewDiffer won't detach this row and the view system is
+ * responsible for ensuring the row is in eventually removed from the parent.
+ *
+ * @return false to opt out from this feature
+ */
+ fun offerToKeepInParentForAnimation(): Boolean
+
+ /**
+ * Called before a node is reattached. Removes the view from its parent
+ * if it was flagged to be kept before.
+ *
+ * @return whether it did a removal
+ */
+ fun removeFromParentIfKeptForAnimation(): Boolean
+
+ /** Called when a node is being reattached */
+ fun resetKeepInParentForAnimation()
}
/**
@@ -90,7 +112,7 @@
}
private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
- sb.append("${indent}{${tree.controller.nodeLabel}}\n")
+ sb.append("$indent{${tree.controller.nodeLabel}}\n")
if (tree.children.isNotEmpty()) {
val childIndent = "$indent "
for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 2073e92..5ff686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -32,6 +32,9 @@
override val view: View
) : NodeController, PipelineDumpable {
override val nodeLabel: String = "<root>"
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
override fun getChildAt(index: Int): View? {
return listContainer.getContainerChildAt(index)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 2c9508e..7b59266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -100,4 +100,7 @@
override val view: View
get() = _view!!
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 9a9941e..18ee481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -86,10 +86,10 @@
}
private fun maybeDetachChild(
- parentNode: ShadeNode,
- parentSpec: NodeSpec?,
- childNode: ShadeNode,
- childSpec: NodeSpec?
+ parentNode: ShadeNode,
+ parentSpec: NodeSpec?,
+ childNode: ShadeNode,
+ childSpec: NodeSpec?
) {
val newParentNode = childSpec?.parent?.let { getNode(it) }
@@ -100,14 +100,27 @@
nodes.remove(childNode.controller)
}
- logger.logDetachingChild(
- key = childNode.label,
- isTransfer = !childCompletelyRemoved,
- isParentRemoved = parentSpec == null,
- oldParent = parentNode.label,
- newParent = newParentNode?.label)
- parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
- childNode.parent = null
+ if (childCompletelyRemoved && parentSpec == null &&
+ childNode.offerToKeepInParentForAnimation()) {
+ // If both the child and the parent are being removed at the same time, then
+ // keep the child attached to the parent for animation purposes
+ logger.logSkipDetachingChild(
+ key = childNode.label,
+ parentKey = parentNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = true
+ )
+ } else {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = parentSpec == null,
+ oldParent = parentNode.label,
+ newParent = newParentNode?.label
+ )
+ parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+ childNode.parent = null
+ }
}
}
@@ -119,6 +132,16 @@
val childNode = getNode(childSpec)
if (childNode.view != currView) {
+ val removedFromParent = childNode.removeFromParentIfKeptForAnimation()
+ if (removedFromParent) {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = false,
+ isParentRemoved = true,
+ oldParent = null,
+ newParent = null
+ )
+ }
when (childNode.parent) {
null -> {
@@ -142,6 +165,8 @@
}
}
+ childNode.resetKeepInParentForAnimation()
+
if (childSpec.children.isNotEmpty()) {
attachChildren(childNode, specMap)
}
@@ -213,4 +238,16 @@
controller.removeChild(child.controller, isTransfer)
child.controller.onViewRemoved()
}
+
+ fun offerToKeepInParentForAnimation(): Boolean {
+ return controller.offerToKeepInParentForAnimation()
+ }
+
+ fun removeFromParentIfKeptForAnimation(): Boolean {
+ return controller.removeFromParentIfKeptForAnimation()
+ }
+
+ fun resetKeepInParentForAnimation() {
+ controller.resetKeepInParentForAnimation()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index b4b9438..1e22c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,6 +43,20 @@
})
}
+ fun logSkipDetachingChild(
+ key: String,
+ parentKey: String?,
+ isTransfer: Boolean,
+ isParentRemoved: Boolean
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ str2 = parentKey
+ bool1 = isTransfer
+ bool2 = isParentRemoved
+ }, { "Skip detaching $str1 from $str2 isTransfer=$bool1 isParentRemoved=$bool2" })
+ }
+
fun logAttachingChild(key: String, parent: String, atIndex: Int) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index b93e150..1eccc98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -240,7 +240,7 @@
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
private int mNotificationColor;
- private ExpansionLogger mLogger;
+ private ExpandableNotificationRowLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
private NotificationEntry mEntry;
@@ -339,7 +339,7 @@
}
}
};
- private boolean mKeepInParent;
+ private boolean mKeepInParentForDismissAnimation;
private boolean mRemoved;
private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
new FloatProperty<ExpandableNotificationRow>("translate") {
@@ -825,6 +825,12 @@
if (mChildrenContainer == null) {
mChildrenContainerStub.inflate();
}
+
+ if (row.keepInParentForDismissAnimation()) {
+ logSkipAttachingKeepInParentChild(row);
+ return;
+ }
+
mChildrenContainer.addNotification(row, childIndex);
onAttachedChildrenCountChanged();
row.setIsChildInGroup(true, this);
@@ -833,6 +839,7 @@
public void removeChildNotification(ExpandableNotificationRow row) {
if (mChildrenContainer != null) {
mChildrenContainer.removeNotification(row);
+ row.setKeepInParentForDismissAnimation(false);
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
@@ -840,6 +847,31 @@
}
/**
+ * Removes the children notifications which were marked to keep for the dismissal animation.
+ */
+ public void removeChildrenWithKeepInParent() {
+ if (mChildrenContainer == null) return;
+
+ List<ExpandableNotificationRow> clonedList = new ArrayList<>(
+ mChildrenContainer.getAttachedChildren());
+ boolean childCountChanged = false;
+ for (ExpandableNotificationRow child : clonedList) {
+ if (child.keepInParentForDismissAnimation()) {
+ mChildrenContainer.removeNotification(child);
+ child.setIsChildInGroup(false, null);
+ child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ child.setKeepInParentForDismissAnimation(false);
+ logKeepInParentChildDetached(child);
+ childCountChanged = true;
+ }
+ }
+
+ if (childCountChanged) {
+ onAttachedChildrenCountChanged();
+ }
+ }
+
+ /**
* Returns the child notification at [index], or null if no such child.
*/
@Nullable
@@ -1361,12 +1393,15 @@
}
}
- public boolean keepInParent() {
- return mKeepInParent;
+ /**
+ * @return if this entry should be kept in its parent during removal.
+ */
+ public boolean keepInParentForDismissAnimation() {
+ return mKeepInParentForDismissAnimation;
}
- public void setKeepInParent(boolean keepInParent) {
- mKeepInParent = keepInParent;
+ public void setKeepInParentForDismissAnimation(boolean keepInParent) {
+ mKeepInParentForDismissAnimation = keepInParent;
}
@Override
@@ -1537,8 +1572,29 @@
mUseIncreasedHeadsUpHeight = use;
}
- public interface ExpansionLogger {
+ /**
+ * Interface for logging {{@link ExpandableNotificationRow} events.}
+ */
+ public interface ExpandableNotificationRowLogger {
+ /**
+ * Called when the notification is expanded / collapsed.
+ */
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+
+ /**
+ * Called when a notification which was previously kept in its parent for the
+ * dismiss animation is finally detached from its parent.
+ */
+ void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
+
+ /**
+ * Called when we want to attach a notification to a new parent,
+ * but it still has the keepInParent flag set, so we skip it.
+ */
+ void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ );
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -1556,7 +1612,7 @@
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
String appName,
String notificationKey,
- ExpansionLogger logger,
+ ExpandableNotificationRowLogger logger,
KeyguardBypassController bypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
@@ -3567,6 +3623,18 @@
});
}
+ private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
+ }
+ }
+
+ private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
+ }
+ }
+
private void setTargetPoint(Point p) {
mTargetPoint = p;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 7b53925..f9e9a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -83,13 +83,11 @@
private final GroupExpansionManager mGroupExpansionManager;
private final RowContentBindStage mRowContentBindStage;
private final NotificationLogger mNotificationLogger;
+ private final NotificationRowLogger mLogBufferLogger;
private final HeadsUpManager mHeadsUpManager;
private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
private final StatusBarStateController mStatusBarStateController;
private final MetricsLogger mMetricsLogger;
-
- private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
- this::logNotificationExpansion;
private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -101,8 +99,32 @@
private final Optional<BubblesManager> mBubblesManagerOptional;
private final SmartReplyConstants mSmartReplyConstants;
private final SmartReplyController mSmartReplyController;
-
private final ExpandableNotificationRowDragController mDragController;
+ private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
+ new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction,
+ boolean expanded) {
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+ }
+
+ @Override
+ public void logKeepInParentChildDetached(
+ NotificationEntry child,
+ NotificationEntry oldParent
+ ) {
+ mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
+ }
+
+ @Override
+ public void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ ) {
+ mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
+ }
+ };
+
@Inject
public ExpandableNotificationRowController(
@@ -110,6 +132,7 @@
ActivatableNotificationViewController activatableNotificationViewController,
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
MetricsLogger metricsLogger,
+ NotificationRowLogger logBufferLogger,
NotificationListContainer listContainer,
NotificationMediaManager mediaManager,
SmartReplyConstants smartReplyConstants,
@@ -163,6 +186,7 @@
mBubblesManagerOptional = bubblesManagerOptional;
mDragController = dragController;
mMetricsLogger = metricsLogger;
+ mLogBufferLogger = logBufferLogger;
mSmartReplyConstants = smartReplyConstants;
mSmartReplyController = smartReplyController;
}
@@ -177,7 +201,7 @@
mRemoteInputViewSubcomponentFactory,
mAppName,
mNotificationKey,
- mExpansionLogger,
+ mLoggerCallback,
mKeyguardBypassController,
mGroupMembershipManager,
mGroupExpansionManager,
@@ -243,10 +267,6 @@
}
};
- private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mNotificationLogger.onExpansionChanged(key, userAction, expanded);
- }
-
@Override
@NonNull
public String getNodeLabel() {
@@ -336,4 +356,29 @@
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
mView.setFeedbackIcon(icon);
}
+
+ @Override
+ public boolean offerToKeepInParentForAnimation() {
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)) {
+ mView.setKeepInParentForDismissAnimation(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeFromParentIfKeptForAnimation() {
+ ExpandableNotificationRow parent = mView.getNotificationParent();
+ if (mView.keepInParentForDismissAnimation() && parent != null) {
+ parent.removeChildNotification(mView);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void resetKeepInParentForAnimation() {
+ mView.setKeepInParentForDismissAnimation(false);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
new file mode 100644
index 0000000..ce11be3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = child.logKey
+ str2 = oldParent.logKey
+ },
+ { "Detach child $str1 kept in parent $str2" }
+ )
+ }
+
+ fun logSkipAttachingKeepInParentChild(child: NotificationEntry, newParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ {
+ str1 = child.logKey
+ str2 = newParent.logKey
+ },
+ { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
+ )
+ }
+}
+
+private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 79700d0..2c096f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2774,6 +2774,10 @@
}
} else {
mSwipedOutViews.remove(child);
+
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).removeChildrenWithKeepInParent();
+ }
}
updateAnimationState(false, child);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 0240bbc..ad4501a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -443,7 +443,11 @@
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
+
row.removeFromTransientContainer();
+ if (row instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index d411e34..ae30ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -94,7 +94,7 @@
overlayContainer = builder.build()
SurfaceControl.Transaction()
- .setLayer(overlayContainer, Integer.MAX_VALUE)
+ .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
.show(overlayContainer)
.apply()
@@ -268,4 +268,12 @@
this.isFolded = isFolded
}
)
+
+ private companion object {
+ private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+
+ // Put the unfold overlay below the rotation animation screenshot to hide the moment
+ // when it is rotated but the rotation of the other windows hasn't happen yet
+ private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 0e4f224..6672469 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -66,7 +66,6 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -138,18 +137,12 @@
/** The currently-selected user. */
val selectedUser: Flow<UserModel>
get() =
- combine(
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { selectedUserInfo, settings ->
+ repository.selectedUserInfo.map { selectedUserInfo ->
val selectedUserId = selectedUserInfo.id
- checkNotNull(
- toUserModel(
- userInfo = selectedUserInfo,
- selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId),
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
+ toUserModel(
+ userInfo = selectedUserInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId)
)
}
@@ -629,7 +622,7 @@
// The guest user should go in the last position.
.sortedBy { it.isGuest }
.mapNotNull { userInfo ->
- toUserModel(
+ filterAndMapToUserModel(
userInfo = userInfo,
selectedUserId = selectedUserId,
canSwitchUsers = canSwitchUsers,
@@ -638,51 +631,65 @@
}
}
- private suspend fun toUserModel(
+ /**
+ * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
+ * to be filtered out.
+ */
+ private suspend fun filterAndMapToUserModel(
userInfo: UserInfo,
selectedUserId: Int,
canSwitchUsers: Boolean,
isUserSwitcherEnabled: Boolean,
): UserModel? {
- val userId = userInfo.id
- val isSelected = userId == selectedUserId
-
return when {
// When the user switcher is not enabled in settings, we only show the primary user.
!isUserSwitcherEnabled && !userInfo.isPrimary -> null
-
// We avoid showing disabled users.
!userInfo.isEnabled -> null
- userInfo.isGuest ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = true,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers,
- isGuest = true,
- )
- userInfo.supportsSwitchToByUser() ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = false,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers || isSelected,
- isGuest = false,
- )
+ // We meet the conditions to return the UserModel.
+ userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
+ toUserModel(userInfo, selectedUserId, canSwitchUsers)
else -> null
}
}
+ /** Maps UserInfo to UserModel based on some parameters. */
+ private suspend fun toUserModel(
+ userInfo: UserInfo,
+ selectedUserId: Int,
+ canSwitchUsers: Boolean
+ ): UserModel {
+ val userId = userInfo.id
+ val isSelected = userId == selectedUserId
+ return if (userInfo.isGuest) {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = true,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers,
+ isGuest = true,
+ )
+ } else {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = false,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers || isSelected,
+ isGuest = false,
+ )
+ }
+ }
+
private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
return withContext(backgroundDispatcher) {
manager.getUserSwitchability(UserHandle.of(selectedUserId))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3b05321..94e3e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -741,22 +742,24 @@
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- CollectionEvent notif0 = postNotif(
+ CollectionEvent groupNotif = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- CollectionEvent notif1 = postNotif(
+ CollectionEvent childNotif = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
- NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry groupEntry = mCollectionListener.getEntry(groupNotif.key);
+ NotificationEntry childEntry = mCollectionListener.getEntry(childNotif.key);
+ ExpandableNotificationRow childRow = mock(ExpandableNotificationRow.class);
+ childEntry.setRow(childRow);
// WHEN the summary is dismissed
- mCollection.dismissNotification(entry0, defaultStats(entry0));
+ mCollection.dismissNotification(groupEntry, defaultStats(groupEntry));
// THEN all members of the group are marked as dismissed locally
- assertEquals(DISMISSED, entry0.getDismissState());
- assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, groupEntry.getDismissState());
+ assertEquals(PARENT_DISMISSED, childEntry.getDismissState());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index 15cf17d..6167b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.matches
+import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -124,6 +129,64 @@
Assert.assertNull(controller3.view.parent)
Assert.assertNull(controller4.view.parent)
Assert.assertNull(controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = controller2)
+ verifyDetachingChildLogged(controller4, oldParent = controller2)
+ verifyDetachingChildLogged(controller5, oldParent = controller2)
+ }
+
+ @Test
+ fun testRemovedGroupsWithKeepInParentAreKeptTogether() {
+ // GIVEN a preexisting tree with a group
+ // AND the group children supports keepInParent
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+
+ // WHEN the new spec removes the entire group
+ applySpecAndCheck(node(controller1))
+
+ // THEN the group children are still attached to their parent
+ Assert.assertEquals(controller2.view, controller3.view.parent)
+ Assert.assertEquals(controller2.view, controller4.view.parent)
+ Assert.assertEquals(controller2.view, controller5.view.parent)
+ verifySkipDetachingChildLogged(controller3, parent = controller2)
+ verifySkipDetachingChildLogged(controller4, parent = controller2)
+ verifySkipDetachingChildLogged(controller5, parent = controller2)
+ }
+
+ @Test
+ fun testReuseRemovedGroupsWithKeepInParent() {
+ // GIVEN a preexisting tree with a dismissed group
+ // AND the group children supports keepInParent
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ applySpecAndCheck(node(controller1))
+
+ // WHEN a new spec is applied which reuses the dismissed views
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2),
+ node(controller3),
+ node(controller4),
+ node(controller5)
+ )
+
+ // THEN the dismissed views can be reused
+ Assert.assertEquals(rootController.view, controller3.view.parent)
+ Assert.assertEquals(rootController.view, controller4.view.parent)
+ Assert.assertEquals(rootController.view, controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = null)
+ verifyDetachingChildLogged(controller4, oldParent = null)
+ verifyDetachingChildLogged(controller5, oldParent = null)
}
@Test
@@ -184,7 +247,30 @@
}
}
+ private fun verifySkipDetachingChildLogged(child: NodeController, parent: NodeController) {
+ verify(logger)
+ .logSkipDetachingChild(
+ key = matches(child.nodeLabel),
+ parentKey = matches(parent.nodeLabel),
+ anyBoolean(),
+ anyBoolean()
+ )
+ }
+
+ private fun verifyDetachingChildLogged(child: NodeController, oldParent: NodeController?) {
+ verify(logger)
+ .logDetachingChild(
+ key = matches(child.nodeLabel),
+ isTransfer = anyBoolean(),
+ isParentRemoved = anyBoolean(),
+ oldParent = oldParent?.let { matches(it.nodeLabel) } ?: isNull(),
+ newParent = isNull()
+ )
+ }
+
private class FakeController(context: Context, label: String) : NodeController {
+ var supportsKeepInParent: Boolean = false
+
override val view: FrameLayout = FrameLayout(context)
override val nodeLabel: String = label
override fun getChildCount(): Int = view.childCount
@@ -209,6 +295,22 @@
override fun onViewAdded() {}
override fun onViewMoved() {}
override fun onViewRemoved() {}
+ override fun offerToKeepInParentForAnimation(): Boolean {
+ return supportsKeepInParent
+ }
+
+ override fun removeFromParentIfKeptForAnimation(): Boolean {
+ if (supportsKeepInParent) {
+ (view.parent as? ViewGroup)?.removeView(view)
+ return true
+ }
+
+ return false
+ }
+
+ override fun resetKeepInParentForAnimation() {
+ supportsKeepInParent = false
+ }
}
private class SpecBuilder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 12cc114..ee8db18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -458,4 +459,79 @@
verify(mNotificationTestHelper.mOnUserInteractionCallback, never())
.registerFutureDismissal(any(), anyInt());
}
+
+ @Test
+ public void testAddChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+
+ group.addChildNotification(child);
+
+ Assert.assertEquals(child, group.getChildNotificationAt(0));
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertTrue(child.isChildInGroup());
+ }
+
+ @Test
+ public void testAddChildNotification_childSkipped() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.addChildNotification(child);
+
+ Assert.assertTrue(group.getAttachedChildren().isEmpty());
+ Assert.assertNotEquals(group, child.getNotificationParent());
+ verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
+ /*child=*/ child.getEntry(),
+ /*newParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildNotification(child);
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
+ /*child=*/ child.getEntry(),
+ /*oldParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
+ throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
+ /*child=*/ any(),
+ /*oldParent=*/ any()
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index ab4ae6a..5fd9448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -73,7 +73,7 @@
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -116,6 +116,7 @@
private final Context mContext;
private final TestableLooper mTestLooper;
private int mId;
+ private final ExpandableNotificationRowLogger mMockLogger;
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
@@ -139,6 +140,7 @@
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = mock(GroupMembershipManager.class);
mGroupExpansionManager = mock(GroupExpansionManager.class);
@@ -197,6 +199,10 @@
mDefaultInflationFlags = defaultInflationFlags;
}
+ public ExpandableNotificationRowLogger getMockLogger() {
+ return mMockLogger;
+ }
+
/**
* Creates a generic row with rounded border.
*
@@ -527,7 +533,7 @@
mock(RemoteInputViewSubcomponent.Factory.class),
APP_NAME,
entry.getKey(),
- mock(ExpansionLogger.class),
+ mMockLogger,
mock(KeyguardBypassController.class),
mGroupMembershipManager,
mGroupExpansionManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index d877209..47efcd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -793,6 +793,19 @@
job.cancel()
}
+ @Test
+ fun `current user is not primary and user switcher is disabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+ var selectedUser: UserModel? = null
+ val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+ assertThat(selectedUser).isNotNull()
+ job.cancel()
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2f8dea7..e282679 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1287,6 +1287,13 @@
}
@Override
+ public int getBatteryHealth() {
+ synchronized (mLock) {
+ return mHealthInfo.batteryHealth;
+ }
+ }
+
+ @Override
public boolean getBatteryLevelLow() {
synchronized (mLock) {
return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index f867808..c9b3597 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -367,7 +367,7 @@
return;
}
- int newState = mOrderedStates[0].getIdentifier();
+ int newState = INVALID_DEVICE_STATE;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
@@ -396,7 +396,7 @@
}
}
- if (newState != mLastReportedState) {
+ if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ab90ef9..0d03133 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -419,6 +419,9 @@
// The current battery level percentage.
private int mBatteryLevel;
+ // The amount of battery drained while the device has been in a dream state.
+ private int mDreamsBatteryLevelDrain;
+
// True if updatePowerStateLocked() is already in progress.
// TODO(b/215518989): Remove this once transactions are in place
private boolean mUpdatePowerStateInProgress;
@@ -451,11 +454,6 @@
@GuardedBy("mEnhancedDischargeTimeLock")
private boolean mEnhancedDischargePredictionIsPersonalized;
- // The battery level percentage at the time the dream started.
- // This is used to terminate a dream and go to sleep if the battery is
- // draining faster than it is charging and the user activity timeout has expired.
- private int mBatteryLevelWhenDreamStarted;
-
// The current dock state.
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -2462,15 +2460,25 @@
final int oldPlugType = mPlugType;
mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
mPlugType = mBatteryManagerInternal.getPlugType();
+ final int oldBatteryLevel = mBatteryLevel;
mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
+ final boolean isOverheat = mBatteryManagerInternal.getBatteryHealth()
+ == BatteryManager.BATTERY_HEALTH_OVERHEAT;
if (DEBUG_SPEW) {
Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
+ ", mIsPowered=" + mIsPowered
+ ", oldPlugType=" + oldPlugType
+ ", mPlugType=" + mPlugType
- + ", mBatteryLevel=" + mBatteryLevel);
+ + ", oldBatteryLevel=" + oldBatteryLevel
+ + ", mBatteryLevel=" + mBatteryLevel
+ + ", isOverheat=" + isOverheat);
+ }
+
+ if (!isOverheat && oldBatteryLevel > 0
+ && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+ mDreamsBatteryLevelDrain += (oldBatteryLevel - mBatteryLevel);
}
if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
@@ -3292,7 +3300,7 @@
// Remember the initial battery level when the dream started.
if (startDreaming && isDreaming) {
- mBatteryLevelWhenDreamStarted = mBatteryLevel;
+ mDreamsBatteryLevelDrain = 0;
if (wakefulness == WAKEFULNESS_DOZING) {
Slog.i(TAG, "Dozing...");
} else {
@@ -3313,16 +3321,15 @@
if (wakefulness == WAKEFULNESS_DREAMING) {
if (isDreaming && canDreamLocked(powerGroup)) {
if (mDreamsBatteryLevelDrainCutoffConfig >= 0
- && mBatteryLevel < mBatteryLevelWhenDreamStarted
- - mDreamsBatteryLevelDrainCutoffConfig
+ && mDreamsBatteryLevelDrain > mDreamsBatteryLevelDrainCutoffConfig
&& !isBeingKeptAwakeLocked(powerGroup)) {
// If the user activity timeout expired and the battery appears
// to be draining faster than it is charging then stop dreaming
// and go to sleep.
Slog.i(TAG, "Stopping dream because the battery appears to "
+ "be draining faster than it is charging. "
- + "Battery level when dream started: "
- + mBatteryLevelWhenDreamStarted + "%. "
+ + "Battery level drained while dreaming: "
+ + mDreamsBatteryLevelDrain + "%. "
+ "Battery level now: " + mBatteryLevel + "%.");
} else {
return; // continue dreaming
@@ -3553,6 +3560,11 @@
mScreenBrightnessBoostInProgress);
}
+ @VisibleForTesting
+ int getDreamsBatteryLevelDrain() {
+ return mDreamsBatteryLevelDrain;
+ }
+
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
new DisplayManagerInternal.DisplayPowerCallbacks() {
@@ -4397,7 +4409,7 @@
pw.println(" mIsPowered=" + mIsPowered);
pw.println(" mPlugType=" + mPlugType);
pw.println(" mBatteryLevel=" + mBatteryLevel);
- pw.println(" mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+ pw.println(" mDreamsBatteryLevelDrain=" + mDreamsBatteryLevelDrain);
pw.println(" mDockState=" + mDockState);
pw.println(" mStayOn=" + mStayOn);
pw.println(" mProximityPositive=" + mProximityPositive);
@@ -4638,8 +4650,8 @@
proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
proto.write(
- PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
- mBatteryLevelWhenDreamStarted);
+ PowerManagerServiceDumpProto.BATTERY_LEVEL_DRAINED_WHILE_DREAMING,
+ mDreamsBatteryLevelDrain);
proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fc15890..66992aa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5261,42 +5261,7 @@
}
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
- // Note that we ignore display frozen since we want the opening / closing transition type
- // can be updated correctly even display frozen, and it's safe since in applyAnimation will
- // still check DC#okToAnimate again if the transition animation is fine to apply.
- // TODO(new-app-transition): Rewrite this logic using WM Shell.
- final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
- final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
- && mVisible == visible;
- if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
- && (appTransition.isTransitionSet()
- || (recentsAnimating && !isActivityTypeHome()))
- // If the visibility is not changed during enter PIP, we don't want to include it in
- // app transition to affect the animation theme, because the Pip organizer will
- // animate the entering PIP instead.
- && !isEnteringPipWithoutVisibleChange) {
- if (visible) {
- displayContent.mOpeningApps.add(this);
- mEnteringAnimation = true;
- } else if (mVisible) {
- displayContent.mClosingApps.add(this);
- mEnteringAnimation = false;
- }
- if ((appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
- // We're launchingBehind, add the launching activity to mOpeningApps.
- final WindowState win = getDisplayContent().findFocusedWindow();
- if (win != null) {
- final ActivityRecord focusedActivity = win.mActivityRecord;
- if (focusedActivity != null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
- focusedActivity);
-
- // Force animation to be loaded.
- displayContent.mOpeningApps.add(focusedActivity);
- }
- }
- }
+ if (deferCommitVisibilityChange(visible)) {
return;
}
@@ -5304,6 +5269,61 @@
updateReportedVisibilityLocked();
}
+ /**
+ * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
+ * Then its visibility will be committed until the transition is ready.
+ */
+ private boolean deferCommitVisibilityChange(boolean visible) {
+ if (!mDisplayContent.mAppTransition.isTransitionSet()) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // Shell transition doesn't use opening/closing sets.
+ return false;
+ }
+ // Defer committing visibility for non-home app which is animating by recents.
+ if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ return false;
+ }
+ }
+ if (mWaitForEnteringPinnedMode && mVisible == visible) {
+ // If the visibility is not changed during enter PIP, we don't want to include it in
+ // app transition to affect the animation theme, because the Pip organizer will
+ // animate the entering PIP instead.
+ return false;
+ }
+
+ // The animation will be visible soon so do not skip by screen off.
+ final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
+ .isKeyguardGoingAway(mDisplayContent.mDisplayId);
+ // Ignore display frozen so the opening / closing transition type can be updated correctly
+ // even if the display is frozen. And it's safe since in applyAnimation will still check
+ // DC#okToAnimate again if the transition animation is fine to apply.
+ if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
+ return false;
+ }
+ if (visible) {
+ mDisplayContent.mOpeningApps.add(this);
+ mEnteringAnimation = true;
+ } else if (mVisible) {
+ mDisplayContent.mClosingApps.add(this);
+ mEnteringAnimation = false;
+ }
+ if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
+ // Add the launching-behind activity to mOpeningApps.
+ final WindowState win = mDisplayContent.findFocusedWindow();
+ if (win != null) {
+ final ActivityRecord focusedActivity = win.mActivityRecord;
+ if (focusedActivity != null) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+ "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
+ focusedActivity);
+ // Force animation to be loaded.
+ mDisplayContent.mOpeningApps.add(focusedActivity);
+ }
+ }
+ }
+ return true;
+ }
+
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 54664f5..6eaeb15e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2593,7 +2593,8 @@
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
- if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+ if (activityOptions != null
+ && activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
targetActivity.mPendingRemoteAnimation =
activityOptions.getRemoteAnimationAdapter();
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 27370bf..adf862b 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -169,7 +169,8 @@
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
- private final TransitionAnimation mTransitionAnimation;
+ @VisibleForTesting
+ final TransitionAnimation mTransitionAnimation;
private @TransitionFlags int mNextAppTransitionFlags = 0;
private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
@@ -315,10 +316,33 @@
setAppTransitionState(APP_STATE_TIMEOUT);
}
+ /**
+ * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
+ */
+ @Nullable
+ Animation getNextAppRequestedAnimation(boolean enter) {
+ final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+ mNextAppTransitionPackage,
+ enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
+ if (mNextAppTransitionBackgroundColor != 0 && a != null) {
+ a.setBackdropColor(mNextAppTransitionBackgroundColor);
+ }
+ return a;
+ }
+
+ /**
+ * Gets the animation background color overridden by app via
+ * {@link #overridePendingAppTransition}.
+ */
@ColorInt int getNextAppTransitionBackgroundColor() {
return mNextAppTransitionBackgroundColor;
}
+ @VisibleForTesting
+ boolean isNextAppTransitionOverrideRequested() {
+ return mNextAppTransitionOverrideRequested;
+ }
+
HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
container.hashCode());
@@ -411,9 +435,12 @@
}
void clear() {
+ clear(true /* clearAppOverride */);
+ }
+
+ private void clear(boolean clearAppOverride) {
mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
mNextAppTransitionOverrideRequested = false;
- mNextAppTransitionPackage = null;
mNextAppTransitionAnimationsSpecs.clear();
mRemoteAnimationController = null;
mNextAppTransitionAnimationsSpecsFuture = null;
@@ -421,6 +448,12 @@
mAnimationFinishedCallback = null;
mOverrideTaskTransition = false;
mNextAppTransitionIsSync = false;
+ if (clearAppOverride) {
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionEnter = 0;
+ mNextAppTransitionExit = 0;
+ mNextAppTransitionBackgroundColor = 0;
+ }
}
void freeze() {
@@ -528,7 +561,7 @@
return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
}
- static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+ private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
int animAttr = 0;
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
@@ -788,11 +821,7 @@
"applyAnimation: anim=%s transit=%s Callers=%s", a,
appTransitionOldToString(transit), Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
- enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
- if (mNextAppTransitionBackgroundColor != 0) {
- a.setBackdropColor(mNextAppTransitionBackgroundColor);
- }
+ a = getNextAppRequestedAnimation(enter);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
+ "isEntrance=%b Callers=%s",
@@ -1032,7 +1061,9 @@
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
if (isTransitionSet() && !mNextAppTransitionIsSync) {
- clear();
+ // ActivityEmbedding animation will run by the app process for which we want to respect
+ // the app override for whether or not to show background color.
+ clear(!isActivityEmbedding /* clearAppOverride */);
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler, isActivityEmbedding);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 12133bc..d5c9e66 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,6 +562,34 @@
}
/**
+ * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
+ * parent {@link Task} before or after the transition.
+ */
+ private boolean transitionContainsTaskFragmentWithBoundsOverride() {
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
+ if (wc.isEmbedded()) {
+ // Contains embedded TaskFragment with bounds changed.
+ return true;
+ }
+ }
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ boolean containsTaskFragmentWithBoundsOverride = false;
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
+ containsTaskFragmentWithBoundsOverride = true;
+ break;
+ }
+ }
+ mTempTransitionWindows.clear();
+ return containsTaskFragmentWithBoundsOverride;
+ }
+
+ /**
* Finds the common parent {@link Task} that is parent of all embedded app windows in the
* current transition.
* @return {@code null} if app windows in the transition are not children of the same Task, or
@@ -664,12 +692,17 @@
if (transitionMayContainNonAppWindows(transit)) {
return false;
}
+ if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+ // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+ // transition fill the Task.
+ return false;
+ }
final Task task = findParentTaskForAllEmbeddedWindows();
final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer, task.mTaskId)
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 38f6a53..3a936a5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4935,9 +4935,8 @@
@Override
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
return okToDisplay(ignoreFrozen, ignoreScreenOn)
- && (mDisplayId != DEFAULT_DISPLAY
- || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
- && getDisplayPolicy().isScreenOnFully();
+ && (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+ && (ignoreFrozen || mDisplayPolicy.isScreenOnFully());
}
static final class TaskForResizePointSearchResult implements Predicate<Task> {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ff09163..0cadc25 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -929,6 +929,7 @@
// Update windowing mode if necessary, e.g. launch into a different windowing mode.
if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
&& candidateTask.getWindowingMode() != windowingMode) {
+ candidateTask.mTransitionController.collect(candidateTask);
candidateTask.setWindowingMode(windowingMode);
}
return candidateTask.getRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 52df3ca..273b4c4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -581,15 +581,7 @@
@Override
boolean isEmbedded() {
- if (mIsEmbedded) {
- return true;
- }
- final WindowContainer<?> parent = getParent();
- if (parent != null) {
- final TaskFragment taskFragment = parent.asTaskFragment();
- return taskFragment != null && taskFragment.isEmbedded();
- }
- return false;
+ return mIsEmbedded;
}
@EmbeddingCheckResult
@@ -2519,6 +2511,22 @@
return mTaskFragmentOrganizer != null;
}
+ /**
+ * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
+ */
+ boolean isEmbeddedWithBoundsOverride() {
+ if (!mIsEmbedded) {
+ return false;
+ }
+ final Task task = getTask();
+ if (task == null) {
+ return false;
+ }
+ final Rect taskBounds = task.getBounds();
+ final Rect taskFragBounds = getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+
/** Whether the Task should be visible. */
boolean isTaskVisibleRequested() {
final Task task = getTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2e716ae..6e4df79 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -36,7 +36,6 @@
import android.annotation.Nullable;
import android.content.Intent;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -133,12 +132,11 @@
new WeakHashMap<>();
/**
- * Map from Task Id to {@link RemoteAnimationDefinition}.
- * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
- * RemoteAnimationDefinition) )
+ * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+ * organized by this organizer.
*/
- private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
- new SparseArray<>();
+ @Nullable
+ private RemoteAnimationDefinition mRemoteAnimationDefinition;
/**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
@@ -427,7 +425,7 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Register task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
- if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (isOrganizerRegistered(organizer)) {
throw new IllegalStateException(
"Replacing existing organizer currently unsupported");
}
@@ -455,7 +453,7 @@
}
@Override
- public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+ public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
@NonNull RemoteAnimationDefinition definition) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -468,20 +466,19 @@
if (organizerState == null) {
throw new IllegalStateException("The organizer hasn't been registered.");
}
- if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
+ if (organizerState.mRemoteAnimationDefinition != null) {
throw new IllegalStateException(
"The organizer has already registered remote animations="
- + organizerState.mRemoteAnimationDefinitions.get(taskId)
- + " for TaskId=" + taskId);
+ + organizerState.mRemoteAnimationDefinition);
}
definition.setCallingPidUid(pid, uid);
- organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
+ organizerState.mRemoteAnimationDefinition = definition;
}
}
@Override
- public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
@@ -495,7 +492,7 @@
return;
}
- organizerState.mRemoteAnimationDefinitions.remove(taskId);
+ organizerState.mRemoteAnimationDefinition = null;
}
}
@@ -505,10 +502,18 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- applyTransaction(wct, transitionType, shouldApplyIndependently);
- final TaskFragmentOrganizerState state = validateAndGetState(
- wct.getTaskFragmentOrganizer());
- state.onTransactionFinished(transactionToken);
+ if (isValidTransaction(wct)) {
+ applyTransaction(wct, transitionType, shouldApplyIndependently);
+ }
+ // Even if the transaction is empty, we still need to invoke #onTransactionFinished
+ // unless the organizer has been unregistered.
+ final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
+ final TaskFragmentOrganizerState state = organizer != null
+ ? mTaskFragmentOrganizerState.get(organizer.asBinder())
+ : null;
+ if (state != null) {
+ state.onTransactionFinished(transactionToken);
+ }
}
}
@@ -517,7 +522,7 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- if (wct.isEmpty()) {
+ if (!isValidTransaction(wct)) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
@@ -527,16 +532,16 @@
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
- * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+ * {@code null} if it doesn't.
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
- @NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ @NonNull ITaskFragmentOrganizer organizer) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
return organizerState != null
- ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+ ? organizerState.mRemoteAnimationDefinition
: null;
}
}
@@ -658,7 +663,7 @@
}
organizer = organizedTf[0].getTaskFragmentOrganizer();
}
- if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (!isOrganizerRegistered(organizer)) {
Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
return;
}
@@ -704,7 +709,7 @@
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
}
- boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+ private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
@@ -741,6 +746,20 @@
return state;
}
+ boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+ if (t.isEmpty()) {
+ return false;
+ }
+ final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+ if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
+ // Transaction from an unregistered organizer should not be applied. This can happen
+ // when the organizer process died before the transaction is applied.
+ Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
+ return false;
+ }
+ return true;
+ }
+
/**
* A class to store {@link ITaskFragmentOrganizer} and its organized
* {@link TaskFragment TaskFragments} with different pending event request.
@@ -1085,16 +1104,7 @@
return false;
}
final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
- if (taskFragment == null) {
- return false;
- }
- final Task parentTask = taskFragment.getTask();
- if (parentTask != null) {
- final Rect taskBounds = parentTask.getBounds();
- final Rect taskFragBounds = taskFragment.getBounds();
- return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
- }
- return false;
+ return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fa1bc54..80357eb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2995,10 +2995,9 @@
// screen empty. Show background color to cover that.
showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
} else {
- // Check whether or not to show backdrop for open/close transition.
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ // Check whether the app has requested to show backdrop for open/close
+ // transition.
+ final Animation a = appTransition.getNextAppRequestedAnimation(enter);
showBackdrop = a != null && a.getShowBackdrop();
}
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 557c0ef..b30fd07 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -403,9 +403,6 @@
*/
void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
- if (!isValidTransaction(wct)) {
- return;
- }
enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
Objects.requireNonNull(wct));
@@ -459,7 +456,7 @@
// calls startSyncSet.
() -> mTransitionController.moveToCollecting(nextTransition),
() -> {
- if (isValidTransaction(wct)) {
+ if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
@@ -1620,18 +1617,6 @@
return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
}
- private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
- if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
- .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
- // Transaction from an unregistered organizer should not be applied. This can happen
- // when the organizer process died before the transaction is applied.
- Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
- + " is no longer registered");
- return false;
- }
- return true;
- }
-
/**
* Makes sure that the transaction only contains operations that are allowed for the
* {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a4..6d2631a 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -65,6 +65,7 @@
private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
DeviceState[].class);
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+ private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
private Context mContext;
private SensorManager mSensorManager;
@@ -268,11 +269,7 @@
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
- @Test
- public void create_sensor() throws Exception {
- Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
- when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
-
+ private DeviceStateProviderImpl create_sensorBasedProvider(Sensor sensor) {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
@@ -310,14 +307,22 @@
+ " <name>" + sensor.getName() + "</name>\n"
+ " <value>\n"
+ " <min-inclusive>180</min-inclusive>\n"
+ + " <max>" + MAX_HINGE_ANGLE_EXCLUSIVE + "</max>\n"
+ " </value>\n"
+ " </sensor>\n"
+ " </conditions>\n"
+ " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
- DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ return DeviceStateProviderImpl.createFromConfig(mContext,
config);
+ }
+
+ @Test
+ public void create_sensor() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
@@ -371,6 +376,40 @@
}
@Test
+ public void test_invalidSensorValues() throws Exception {
+ // onStateChanged() should not be triggered by invalid sensor values.
+
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+ Mockito.clearInvocations(listener);
+
+ // First, switch to a non-default state.
+ SensorEvent event1 = mock(SensorEvent.class);
+ event1.sensor = sensor;
+ FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
+ provider.onSensorChanged(event1);
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+ SensorEvent event2 = mock(SensorEvent.class);
+ event2.sensor = sensor;
+ FieldSetter.setField(event2, event2.getClass().getField("values"),
+ new float[]{MAX_HINGE_ANGLE_EXCLUSIVE});
+
+ provider.onSensorChanged(event2);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+ }
+
+ @Test
public void create_invalidSensor() throws Exception {
Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of());
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 32772a4..a42d009 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -375,6 +375,18 @@
mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
}
+ private void setBatteryLevel(int batteryLevel) {
+ when(mBatteryManagerInternalMock.getBatteryLevel())
+ .thenReturn(batteryLevel);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
+ private void setBatteryHealth(int batteryHealth) {
+ when(mBatteryManagerInternalMock.getBatteryHealth())
+ .thenReturn(batteryHealth);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
private void setAttentiveTimeout(int attentiveTimeoutMillis) {
Settings.Secure.putInt(
mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
@@ -399,6 +411,12 @@
.thenReturn(disable);
}
+ private void setDreamsBatteryLevelDrainConfig(int threshold) {
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+ threshold);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -938,6 +956,41 @@
}
@Test
+ public void testBatteryDrainDuringDream() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setMinimumScreenOffTimeoutConfig(100);
+ setDreamsBatteryLevelDrainConfig(5);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setBatteryLevel(100);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ setBatteryLevel(90);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+ // If battery overheat protection is enabled, we shouldn't count battery drain
+ setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ setBatteryLevel(70);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+ }
+
+ @Test
public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
final String tag = "acq_causes_wakeup";
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ff5622f..d3e638f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3091,6 +3091,17 @@
assertTrue(activity.mVisibleRequested);
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+
+ // There should still be animation (add to opening) if keyguard is going away while the
+ // screen is off because it will be visible after screen is turned on by unlocking.
+ mDisplayContent.mOpeningApps.remove(activity);
+ mDisplayContent.mClosingApps.remove(activity);
+ activity.commitVisibility(false /* visible */, false /* performLayout */);
+ mDisplayContent.getDisplayPolicy().screenTurnedOff();
+ final KeyguardController controller = mSupervisor.getKeyguardController();
+ doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
+ activity.setVisibility(true);
+ assertTrue(mDisplayContent.mOpeningApps.contains(activity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0332c4b..43e79f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
@@ -56,6 +57,7 @@
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.gui.DropInputMode;
import android.os.Binder;
import android.os.IBinder;
@@ -918,7 +920,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -935,11 +937,77 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is not embedded.
+ assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation is not run by the remote handler because the activity is filling the Task.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is embedded.
+ taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect embeddedBounds = new Rect(task.getBounds());
+ embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+ taskFragment.setBounds(embeddedBounds);
+ assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing non-embedded activity.
final ActivityRecord closingActivity = createActivityRecord(task);
@@ -964,7 +1032,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -991,7 +1059,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing activity in Task1.
final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
@@ -1015,7 +1083,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1043,7 +1111,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1069,7 +1137,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activities, one is trusted embedded, and the other
// one is untrusted embedded.
@@ -1128,7 +1196,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1168,7 +1236,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1259,7 +1327,7 @@
}
/** Registers remote animation for the organizer. */
- private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
TestRemoteAnimationRunner remoteAnimationRunner) {
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
remoteAnimationRunner, 10, 1);
@@ -1268,9 +1336,10 @@
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
- definition);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
}
private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 32c95fa..8cfe503 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -49,6 +49,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -67,11 +68,14 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.TransitionAnimation;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -495,6 +499,80 @@
assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
}
+ @Test
+ public void testGetNextAppTransitionBackgroundColor() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+
+ // No override by default.
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+
+ // Override with a custom color.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ final int testColor = 123;
+ mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
+ 0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Background color should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Background color should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+ }
+
+ @Test
+ public void testGetNextAppRequestedAnimation() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+ final String packageName = "testPackage";
+ final int enterAnimResId = 1;
+ final int exitAnimResId = 2;
+ final int testColor = 123;
+ final Animation enterAnim = mock(Animation.class);
+ final Animation exitAnim = mock(Animation.class);
+ final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
+ spyOn(transitionAnimation);
+ doReturn(enterAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, enterAnimResId);
+ doReturn(exitAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, exitAnimResId);
+
+ // No override by default.
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+
+ // Override with a custom animation.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
+ testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Custom animation should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Custom animation should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ }
+
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index c535182..2b49314 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -474,13 +474,13 @@
@Test
public void testRegisterRemoteAnimations() {
- mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
+ mController.registerRemoteAnimations(mIOrganizer, mDefinition);
- assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
- mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
+ mController.unregisterRemoteAnimations(mIOrganizer);
- assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
}
@Test
@@ -822,6 +822,21 @@
}
@Test
+ public void testOnTransactionHandled_skipTransactionForUnregisterOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mController.onTransactionHandled(new Binder(), mTransaction,
+ getTransitionType(mTransaction), false /* shouldApplyIndependently */);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testOrganizerRemovedWithPendingEvents() {
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()