【问题分析】CtsWindowManagerDeviceAnimations【Android15】
1 case逻辑分析
CtsWindowManagerDeviceAnimations.testRightEdgeExtensionWorksDuringActivityTransition报错:
java.lang.AssertionError: No screenshot of the activity transition passed the assertions :: ColorCheckResult{isFailure=true, firstWrongPixel=Point(450, 1263), expectedColor=Color(1.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1), actualColor=Color(0.0, 0.0, 0.0, 0.0, sRGB IEC61966-2.1)},
根据case的内容大概理解一下这个case的测试逻辑:
启动一个名为EdgeExtensionActivity的Activity,这个Activity重写了动画,当播放动画时,EdgeExtensionActivity在X轴方向上被缩放到50%,并且边缘像素延伸到屏幕右边。
因为这个播放动画的Activity是一半蓝,一半红的,我们预期前25%的像素列是蓝色的(来自Activity),然后剩下的75%的像素列是红色的(25%来自Activity,50%来自edge extenstion)。
然后在动画过程中截图,做个对比:
左边是我们的机器有问题,可以看到压缩后左半屏右边的红色边缘是没有延伸到右半屏的。
右边是pixel没问题,可以看到压缩后左半屏右边的红色像素延伸到了右半屏。
再看报错内容:
预期在Point(450, 1263)位置的像素颜色应该是:
Color(1.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1),即红色。
但是实际上是:
Color(0.0, 0.0, 0.0, 0.0, sRGB IEC61966-2.1),即黑色。
所以case fail。
2 本地Demo模拟CTS case
这个问题刚拿到后没有什么头绪,而且没有搞懂这个动画是怎么实现的,先看下能否本地写一个Demo模拟一下case逻辑。
继续梳理case的逻辑,这个case的核心逻辑为,创建一个名为EdgeExtensionActivity的Activity,这个Activity是一半蓝色,一半红色:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent">
<View android:background="#0000ff" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/>
<View android:background="#ff0000" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/>
</LinearLayout>
就像这样:
然后这个Activity在onResume中调用overridePendingTransition重写了enter动画和exit动画:
@Override
protected void onResume() {
super.onResume();
mPendingEnterRes = R.anim.edge_extension_right;
mPendingExitRes = R.anim.alpha_0;
overridePendingTransition(mPendingEnterRes, mPendingExitRes);
}
这里我们只关注enter动画,动画样式为自定义的R.anim.edge_extension_right:
这个动画样式我在aosp中没找到,反编译了CTS的CtsWindowManagerDeviceAnimations.apk后得到:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
<alpha android:interpolator="@android:interpolator/linear" android:duration="5000" android:fillBefore="true" android:fillAfter="true" android:startOffset="0" android:fromAlpha="1" android:toAlpha="1" android:fillEnabled="true"/>
<scale android:interpolator="@android:interpolator/linear" android:duration="5000" android:startOffset="0" android:fromXScale="0.5" android:toXScale="0.5" android:fromYScale="1" android:toYScale="1"/>
<extend android:interpolator="@android:interpolator/linear" android:duration="5000" android:startOffset="0" android:fromExtendLeft="0" android:fromExtendTop="0" android:fromExtendRight="100%" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" android:toExtendRight="100%" android:toExtendBottom="0"/>
</set>
定义了三种动画:
1)、alpha:前后没有变化。
2)、scale:动画开始的时候X轴方向上缩放到0.5,并且这个缩放比例不变,一直保持到动画结束。
3)、extend,有8个特定的描述该类型动画的属性:
- fromExtendLeft和toExtendLeft。
- fromExtendTop和toExtendTop。
- fromExtendRight和toExtendRight。
- fromExtendBottom和toExtendBottom。
并且设置了fromExtendRight和toExtendRight都是100%,这个属性暂时还不清楚是如何作用。
因为pixel上是正常的,因此在Demo上做了一些修改后,看到:
进行对比,依稀对extend类型的动画有了一点理解,即将Activity的边缘像素延伸到屏幕的边缘。
再进行一下实验,将整个过程中将X和Y轴方向上都缩放0.5,然后fromExtendRight和fromExtendBottom设置为10%,toExtendRight和toExtendBottom设置为100%,看下是什么效果:
的确是把Activity的边缘像素延伸到屏幕的边缘的感觉。
再把这个Demo装到我们的机器,发现5s的动画过程中一直都是以下状态:
只看到了scale的动画效果,但是没有extend的动画效果。
这么一对比,看起来似乎是我们的机器,extend这个类型的动画压根就没有生效。
3 ExtendAnimation分析
在aosp中搜索fromExtendRight等关键字,看到使用的地方在ExtendAnimation:
在ExtendAnimation的构造方法中被解析,而ExtendAnimation创建的地方在AnimationUtils.createAnimationFromXml:
也很好理解,如果动画的标签是extend,那么创建ExtendAnimation类型的动画。
再看fromExtendRight和mToRightValue这些属性被解析出来后,都用在什么地方,搜索了一下代码,只有一处,在ExtendAnimation.initialize:
调用堆栈为:
用来初始化ExtendAnimation的两个Insets类型的成员变量mFromInsets和mToInsets。
而成员变量mFromInsets和mToInsets使用的地方主要是在ExtendAnimation.applyTransformation和ExtendAnimation.hasExtension:
applyTransformation这个方法很熟悉了,Animation的子类通过实现这个方法,来计算动画过程中特定时间点下的动画要应用的transformation。
看下调用堆栈为:
看到关键点在TransitionAnimationHelper.edgeExtendWindow:
这里看下edgeBounds和extensionRect的值,我的屏幕是720 * 1612,基于我们设置的extend动画参数:
<extend android:interpolator="@android:interpolator/linear" android:duration="5000" android:startOffset="0" android:fromExtendLeft="0" android:fromExtendTop="0" android:fromExtendRight="10%" android:fromExtendBottom="0" android:toExtendLeft="0" android:toExtendTop="0" android:toExtendRight="100%" android:toExtendBottom="0"/>
得到edgeBounds=Rect(719, 0 - 720, 1612), extensionRect=Rect(0, 0 - 720, 1612)
再看TransitionAnimationHelper.createExtensionSurface方法:
大致可以理解一下,将整个屏幕截图,然后截取edgeBounds对应的区域,然后从起始位置开始,一直拓展到extensionRect区域。
回到我们的问题,既然ExtendAnimation没有生效,那么大概率是这里的edgeBuffer返回了null,后续继续在SurfaceFlinger.captureLayers打印log,果然,在SurfaceFlinger的这里返回了:
发现截图的进程对应的App没有声明该权限,android.permission.CAPTURE_BLACKOUT_CONTENT,所以截图失败。
看了下我们的SystemUI,是没有声明这个权限的,反编译pixel的SystemUIGoogle,发现:
pixel的SystemUI(据SystemUI的同事说pixel的SystemUI不是aosp的那个SystemUI)是添加了这个权限的,所以pixel的SystemUI可以截图,并且后续正常运行ExtendAnimation。
另外一提aosp的SystemUI,即“ /frameworks/base/packages/SystemUI/AndroidManifest.xml”也是没有声明这个权限的,这不是google挖坑吗?只给自己pixel的SystemUI添加这个权限?
最后再用winscope看下画红色的是什么玩意:
“Right Edge Extension”下的一个名为“bbq-wrapper”的Layer。