Notify client to clear intermediate rotated adjustments

For example: launch a landscape activity while device is portrait,
and then launch another portrait activity before previous transition
is done. If the landscape activity is destroyed before sending the
fixed rotation adjustment for restoration, the adjustment is always
retained by client side app. That causes the Display objects
associated with application Resources to always return rotation from
the adjustment.

This change notifies client to clear the rotated adjustments if the
the display won't be rotated by the next top activity before the
transition is done, so the app can get the correct rotation in time.
The transform will be cleared at the end of transition animation so
the closing animation won't jump cut with rotation change. E.g.
launch an activity in different orientation and press home or back
key before the launch animation is finished.

Also simply a bit for the path of clearing fixed rotation state to
avoid sending duplicated adjustments.

Bug: 161519202
Bug: 168263090
Bug: 177390830
Test: DisplayContentTests#testClearIntermediateFixedRotationAdjustments

Change-Id: Ica074604df3e74eabbdef931531abe51855103e5
Merged-In: Ica074604df3e74eabbdef931531abe51855103e5
This commit is contained in:
Riddle Hsu
2021-02-22 13:52:07 +08:00
parent 7e2d8b8b13
commit 8fea9c8e89
3 changed files with 43 additions and 7 deletions

View File

@@ -1513,6 +1513,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
final int rotation = rotationForActivityInDifferentOrientation(r);
if (rotation == ROTATION_UNDEFINED) {
// The display rotation won't be changed by current top activity. The client side
// adjustments of previous rotated activity should be cleared earlier. Otherwise if
// the current top is in the same process, it may get the rotated state. The transform
// will be cleared later with transition callback to ensure smooth animation.
if (hasTopFixedRotationLaunchingApp()) {
mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
}
return false;
}
if (!r.getParent().matchParentBounds()) {

View File

@@ -622,11 +622,6 @@ class WindowToken extends WindowContainer<WindowState> {
state.mIsTransforming = false;
if (applyDisplayRotation != null) {
applyDisplayRotation.run();
} else {
// The display will not rotate to the rotation of this container, let's cancel them.
for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
state.mAssociatedTokens.get(i).cancelFixedRotationTransform();
}
}
// The state is cleared at the end, because it is used to indicate that other windows can
// use seamless rotation when applying rotation to display.
@@ -634,11 +629,15 @@ class WindowToken extends WindowContainer<WindowState> {
final WindowToken token = state.mAssociatedTokens.get(i);
token.mFixedRotationTransformState = null;
token.notifyFixedRotationTransform(false /* enabled */);
if (applyDisplayRotation == null) {
// Notify cancellation because the display does not change rotation.
token.cancelFixedRotationTransform();
}
}
}
/** Notifies application side to enable or disable the rotation adjustment of display info. */
private void notifyFixedRotationTransform(boolean enabled) {
void notifyFixedRotationTransform(boolean enabled) {
FixedRotationAdjustments adjustments = null;
// A token may contain windows of the same processes or different processes. The list is
// used to avoid sending the same adjustments to a process multiple times.
@@ -682,7 +681,6 @@ class WindowToken extends WindowContainer<WindowState> {
// The window may be detached or detaching.
return;
}
notifyFixedRotationTransform(false /* enabled */);
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);

View File

@@ -88,6 +88,7 @@ import static org.mockito.Mockito.doCallRealMethod;
import android.annotation.SuppressLint;
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
@@ -1345,6 +1346,36 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(recentsActivity.hasFixedRotationTransform());
}
@Test
public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
final ActivityRecord activity = new ActivityTestsBase.StackBuilder(mWm.mRoot)
.setDisplay(mDisplayContent).build().getTopMostActivity();
mDisplayContent.setFixedRotationLaunchingApp(activity,
(mDisplayContent.getRotation() + 1) % 4);
// Create a window so FixedRotationAdjustmentsItem can be sent.
createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin");
final ActivityRecord activity2 = new ActivityTestsBase.StackBuilder(mWm.mRoot)
.setDisplay(mDisplayContent).build().getTopMostActivity();
activity2.setVisible(false);
clearInvocations(mWm.mAtmService.getLifecycleManager());
// The first activity has applied fixed rotation but the second activity becomes the top
// before the transition is done and it has the same rotation as display, so the dispatched
// rotation adjustment of first activity must be cleared.
mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2,
false /* checkOpening */);
final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor =
ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
verify(mWm.mAtmService.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
eq(activity.app.getThread()), adjustmentsCaptor.capture());
// The transformation is kept for animation in real case.
assertTrue(activity.hasFixedRotationTransform());
final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
activity.token, null /* fixedRotationAdjustments */);
// The captor may match other items. The first one must be the item to clear adjustments.
assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0));
}
@Test
public void testRemoteRotation() {
DisplayContent dc = createNewDisplay();