Merge "Remove clip adjustment to fix bottom screen flicker issue. Add a flicker test to verify animation background is no longer visible through the transition after the fix." into udc-qpr-dev am: 3dfd9e6c02

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23660587

Change-Id: Id9ac2f2565cdcc2a7b48cc1331a61cfe82366088
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
An An Yu
2023-07-24 19:18:03 +00:00
committed by Automerger Merge Worker
10 changed files with 270 additions and 67 deletions

View File

@@ -50,7 +50,7 @@ class ActivityEmbeddingAnimationAdapter {
final SurfaceControl mLeash; final SurfaceControl mLeash;
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */ /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull @NonNull
private final Rect mWholeAnimationBounds = new Rect(); final Rect mWholeAnimationBounds = new Rect();
/** /**
* Area in absolute coordinate that should represent all the content to show for this window. * Area in absolute coordinate that should represent all the content to show for this window.
* This should be the end bounds for opening window, and start bounds for closing window in case * This should be the end bounds for opening window, and start bounds for closing window in case
@@ -229,20 +229,7 @@ class ActivityEmbeddingAnimationAdapter {
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y); mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha()); t.setAlpha(mLeash, mTransformation.getAlpha());
t.setWindowCrop(mLeash, mWholeAnimationBounds.width(), mWholeAnimationBounds.height());
// The following applies an inverse scale to the clip-rect so that it crops "after" the
// scale instead of before.
mVecs[1] = mVecs[2] = 0;
mVecs[0] = mVecs[3] = 1;
mTransformation.getMatrix().mapVectors(mVecs);
mVecs[0] = 1.f / mVecs[0];
mVecs[3] = 1.f / mVecs[3];
final Rect clipRect = mTransformation.getClipRect();
mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
t.setCrop(mLeash, mRect);
} }
} }
} }

View File

@@ -26,7 +26,6 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationSet; import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.animation.ClipRectAnimation;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator; import android.view.animation.LinearInterpolator;
import android.view.animation.ScaleAnimation; import android.view.animation.ScaleAnimation;
@@ -189,14 +188,6 @@ class ActivityEmbeddingAnimationSpec {
startBounds.top - endBounds.top, 0); startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION); endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate); endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
final Rect startClip = new Rect(startBounds);
final Rect endClip = new Rect(endBounds);
startClip.offsetTo(0, 0);
endClip.offsetTo(0, 0);
final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(clipAnim);
endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(), endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
parentBounds.height()); parentBounds.height());
endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting); endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);

View File

@@ -17,9 +17,12 @@
package com.android.server.wm.flicker.activityembedding package com.android.server.wm.flicker.activityembedding
import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.Before import org.junit.Before
import org.junit.Test
abstract class ActivityEmbeddingTestBase(flicker: LegacyFlickerTest) : BaseTest(flicker) { abstract class ActivityEmbeddingTestBase(flicker: LegacyFlickerTest) : BaseTest(flicker) {
val testApp = ActivityEmbeddingAppHelper(instrumentation) val testApp = ActivityEmbeddingAppHelper(instrumentation)
@@ -29,4 +32,14 @@ abstract class ActivityEmbeddingTestBase(flicker: LegacyFlickerTest) : BaseTest(
// The test should only be run on devices that support ActivityEmbedding. // The test should only be run on devices that support ActivityEmbedding.
ActivityEmbeddingAppHelper.assumeActivityEmbeddingSupportedDevice() ActivityEmbeddingAppHelper.assumeActivityEmbeddingSupportedDevice()
} }
/** Asserts the background animation layer is never visible during bounds change transition. */
@Presubmit
@Test
fun backgroundLayerNeverVisible() {
val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
flicker.assertLayers {
isInvisible(backgroundColorLayer)
}
}
} }

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm.flicker.activityembedding
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test changing split ratio at runtime on a horizona split.
*
* Setup: Launch A|B in horizontal split with B being the secondary activity, by default A and B
* windows are equal in size. B is on the top and A is on the bottom.
* Transitions:
* Change the split ratio to A:B=0.7:0.3, expect bounds change for both A and B.
*
* To run this test: `atest FlickerTests:HorizontalSplitChangeRatioTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
tapl.setExpectedRotationCheckEnabled(false)
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivityHorizontally(wmHelper)
startDisplayBounds =
wmHelper.currentState.layerState.physicalDisplayBounds
?: error("Display not found")
}
transitions {
testApp.changeSecondaryActivityRatio(wmHelper)
}
teardown {
tapl.goHome()
testApp.exit(wmHelper)
}
}
/** Assert the Main activity window is always visible. */
@Presubmit
@Test
fun mainActivityWindowIsAlwaysVisible() {
flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
}
/** Assert the Main activity window is always visible. */
@Presubmit
@Test
fun mainActivityLayerIsAlwaysVisible() {
flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
}
/** Assert the Secondary activity window is always visible. */
@Presubmit
@Test
fun secondaryActivityWindowIsAlwaysVisible() {
flicker.assertWm {
isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) }
}
/** Assert the Secondary activity window is always visible. */
@Presubmit
@Test
fun secondaryActivityLayerIsAlwaysVisible() {
flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) }
}
/** Assert the Main and Secondary activity change height during the transition. */
@Presubmit
@Test
fun secondaryActivityAdjustsHeightRuntime() {
flicker.assertLayersStart {
val topLayerRegion =
this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
val bottomLayerRegion =
this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
.that(topLayerRegion.region.height).isEqual(bottomLayerRegion.region.height)
check { "width" }
.that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
}
flicker.assertLayersEnd {
val topLayerRegion =
this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
val bottomLayerRegion =
this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
.that(topLayerRegion.region.height).isLower(bottomLayerRegion.region.height)
check { "height" }
.that(
topLayerRegion.region.height / 0.3f -
bottomLayerRegion.region.height / 0.7f)
.isLower(0.1f)
check { "width" }
.that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
}
}
companion object {
/** {@inheritDoc} */
private var startDisplayBounds = Rect.EMPTY
/**
* Creates the test configurations.
*
* See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
* navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
}

View File

@@ -142,14 +142,6 @@ class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
} }
} }
/** Assert the background animation layer is never visible during transition. */
@Presubmit
@Test
fun backgroundLayerNeverVisible() {
val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
flicker.assertLayers { isInvisible(backgroundColorLayer) }
}
companion object { companion object {
/** {@inheritDoc} */ /** {@inheritDoc} */
private var startDisplayBounds = Rect.EMPTY private var startDisplayBounds = Rect.EMPTY

View File

@@ -45,18 +45,22 @@ constructor(
* based on the split pair rule. * based on the split pair rule.
*/ */
fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) { fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
val launchButton = launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_button")
uiDevice.wait( }
Until.findObject(By.res(getPackage(), "launch_secondary_activity_button")),
FIND_TIMEOUT /**
) * Clicks the button to launch the secondary activity in RTL, which should split with the main
require(launchButton != null) { "Can't find launch secondary activity button on screen." } * activity based on the split pair rule.
launchButton.click() */
wmHelper fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
.StateSyncBuilder() launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button")
.withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) }
.withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
.waitForAndVerify() /**
* Clicks the button to launch the secondary activity in a horizontal split.
*/
fun launchSecondaryActivityHorizontally(wmHelper: WindowManagerStateHelper) {
launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_horizontally_button")
} }
/** Clicks the button to launch a third activity over a secondary activity. */ /** Clicks the button to launch a third activity over a secondary activity. */
@@ -113,6 +117,28 @@ constructor(
.waitForAndVerify() .waitForAndVerify()
} }
/**
* Clicks the button to toggle the split ratio of secondary activity.
*/
fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
Until.findObject(
By.res(getPackage(),
"toggle_split_ratio_button")),
FIND_TIMEOUT
)
require(launchButton != null) {
"Can't find toggle ratio for secondary activity button on screen."
}
launchButton.click()
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
.withTransitionSnapshotGone()
.waitForAndVerify()
}
fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) { fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
val pipButton = val pipButton =
uiDevice.wait( uiDevice.wait(
@@ -149,18 +175,12 @@ constructor(
.waitForAndVerify() .waitForAndVerify()
} }
/** private fun launchSecondaryActivityFromButton(
* Clicks the button to launch the secondary activity in RTL, which should split with the main wmHelper: WindowManagerStateHelper, buttonName: String) {
* activity based on the split pair rule.
*/
fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
val launchButton = val launchButton =
uiDevice.wait( uiDevice.wait(Until.findObject(By.res(getPackage(), buttonName)), FIND_TIMEOUT)
Until.findObject(By.res(getPackage(), "launch_secondary_activity_rtl_button")),
FIND_TIMEOUT
)
require(launchButton != null) { require(launchButton != null) {
"Can't find launch secondary activity rtl button on screen." "Can't find launch secondary activity button : " + buttonName + "on screen."
} }
launchButton.click() launchButton.click()
wmHelper wmHelper

View File

@@ -37,6 +37,14 @@
android:tag="RIGHT_TO_LEFT" android:tag="RIGHT_TO_LEFT"
android:text="Launch Secondary Activity in RTL" /> android:text="Launch Secondary Activity in RTL" />
<Button
android:id="@+id/launch_secondary_activity_horizontally_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:onClick="launchSecondaryActivity"
android:tag="BOTTOM_TO_TOP"
android:text="Launch Secondary Activity Horizontally" />
<Button <Button
android:id="@+id/launch_placeholder_split_button" android:id="@+id/launch_placeholder_split_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -35,6 +35,14 @@
android:onClick="launchThirdActivity" android:onClick="launchThirdActivity"
android:text="Launch a third activity" /> android:text="Launch a third activity" />
<ToggleButton
android:id="@+id/toggle_split_ratio_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:textOn="Ratio 0.5"
android:textOff="Ratio 0.3"
android:checked="false" />
<Button <Button
android:id="@+id/secondary_enter_pip_button" android:id="@+id/secondary_enter_pip_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -22,6 +22,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull;
import androidx.window.embedding.ActivityFilter; import androidx.window.embedding.ActivityFilter;
import androidx.window.embedding.ActivityRule; import androidx.window.embedding.ActivityRule;
import androidx.window.embedding.EmbeddingAspectRatio; import androidx.window.embedding.EmbeddingAspectRatio;
@@ -152,6 +153,9 @@ public class ActivityEmbeddingMainActivity extends Activity {
if (layoutDirectionStr.equals(LayoutDirection.LEFT_TO_RIGHT.toString())) { if (layoutDirectionStr.equals(LayoutDirection.LEFT_TO_RIGHT.toString())) {
return LayoutDirection.LEFT_TO_RIGHT; return LayoutDirection.LEFT_TO_RIGHT;
} }
if (layoutDirectionStr.equals(LayoutDirection.BOTTOM_TO_TOP.toString())) {
return LayoutDirection.BOTTOM_TO_TOP;
}
if (layoutDirectionStr.equals(LayoutDirection.RIGHT_TO_LEFT.toString())) { if (layoutDirectionStr.equals(LayoutDirection.RIGHT_TO_LEFT.toString())) {
return LayoutDirection.RIGHT_TO_LEFT; return LayoutDirection.RIGHT_TO_LEFT;
} }

View File

@@ -22,6 +22,11 @@ import android.app.PictureInPictureParams;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.ToggleButton;
import androidx.window.embedding.SplitAttributes;
import androidx.window.embedding.SplitAttributesCalculatorParams;
import androidx.window.embedding.SplitController;
/** /**
* Activity to be used as the secondary activity to split with * Activity to be used as the secondary activity to split with
@@ -29,6 +34,8 @@ import android.view.View;
*/ */
public class ActivityEmbeddingSecondaryActivity extends Activity { public class ActivityEmbeddingSecondaryActivity extends Activity {
private SplitController mSplitController;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -41,6 +48,27 @@ public class ActivityEmbeddingSecondaryActivity extends Activity {
finish(); finish();
} }
}); });
mSplitController = SplitController.getInstance(this);
final ToggleButton splitRatio = findViewById(R.id.toggle_split_ratio_button);
mSplitController.setSplitAttributesCalculator(params -> {
return new SplitAttributes.Builder()
.setSplitType(
SplitAttributes.SplitType.ratio(
splitRatio.isChecked() ? 0.7f : 0.5f)
)
.setLayoutDirection(
params.getDefaultSplitAttributes()
.getLayoutDirection())
.build();
});
splitRatio.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// This triggers a recalcuation of splitatributes.
mSplitController.invalidateTopVisibleSplitAttributes();
}
});
findViewById(R.id.secondary_enter_pip_button).setOnClickListener( findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override