浅谈Google认证失败项分析
AndroidTV机顶盒项目的Google认证包含8项测试:CTS、GTS、STS、VTS、CTS-ON-GSI、TVTS、SmokeTest、CtsVerifier、BTS,详细的规范要求见文档:
GTVS Requirements
、
ATV Help#Android TV Certification
。
本文着重讲述一个AndroidTV项目认证流程中的技术环节,即xts失败项分析。尽管这些测试内容方法以及测试用例实现不一,但其分析解决流程都是相同的:
确认失败项、确认测试内容、修复
。
ps:BTS本文不讨论。
二、确认失败项
一份测试报告提供出来,它的失败项全都需要开发人员分析吗?往往不是这样。
经验来说这其中会有许多的
环境相关项
。比如未接camera、HDMI;未接入支持ipv6的wifi网络或网络环境太差;未恢复出厂retry等等。还有Google认为可以失败的
waiver
项(一般是Google的测试用例bug导致)。
以下是几个经常见到的
环境相关项
。
armeabi-v7a CtsMediaTestCases
android.hardware.camera2.cts.CameraManagerTest#testCameraManagerGetDeviceIdList
junit.framework.AssertionFailedError: External camera is not connected on device with FEATURE_CAMERA_EXTERNAL
android.hardware.cts.CameraTest#testCameraExternalConnected
junit.framework.AssertionFailedError: Devices with external camera support must have a camera connected for testing
以下是当前的一条cts-on-gsi测试
wavier项
,适用2020-10月system.img与VTS_9.0_R14工具。waiver项是动态更新的,Google收到各厂商反馈后,会在下个版本修复,所以要及时跟进同步。
armeabi-v7a CtsOsTestCases
基本上,像cts这样测试套件自动化的测试,调整尽可能ok的环境恢复出厂retry多次至失败项不再减少时,可以认为是真正的失败项了。就可以发出
报告与日志
安排对应模块开发人员分析。
冒烟测试和ctsVerifier是手工测试,若测试不过,基本是有问题的,应直接介入分析。
三、分析前的一些背景知识
3.1 xts测试的工具、源码及形式
自动化的几个测试都是类似的,apk的形式安装到Android设备中进行测试,sts有部分会推送二进制到设备执行,vts会执行一些命令来获取底层信息。
cts测试套件,公开下载。
Compatibility Test Suite Downloads
设备端以JUnit tests和apk的形式测试。
Types of test cases
包含单元测试与功能测试
Android平台兼容性。CDD + Android SDK/NDK/APIs
sts测试套件,非公开。同安全补丁一起发布
Security Test Suite
部分AOSP
设备端测试junit tests和主机端二进制推送值设备测试。
Security Test Suite#Types of test cases
测试安全漏洞。每个月和安全补丁一起更新。Security patch compliance
vts测试套件,非公开。
Vendor Test Suite (VTS) and Generic System Image (GSI)
主要是执行shell命令
Device shell commands
替换GSI后测试框架以下的部分。HAL、驱动与内核。Treble compliance
CTS-ON-GSI
同vts。
替换GSI后测试一遍CTS。Treble compliance
gts测试套件,非公开。
Downloading and Running GTS
反编译获取
用于验证GMS应用程序是否正确集成。Platform implementation for GTVS services
tvts测试套件,非公开。
TV Test Suite
反编译获取
用于验证GMS应用程序性能。Performance
CtsVerfier
CtsVerfier测试工具包,公开下载
Compatibility Test Suite Downloads
安装CtsVerifier.apk后按条测,半自动
cts的补充测试,需要人工判断。CDD + Android SDK/NDK/APIs
SmokeTest
表格,非公开。
Smoke Test plan
、
YouTube and Play Movies Video Test Pack
、
YouTube and Play Movies Video Test Pack
按照表格指示逐条人工测试
验证基本功能是否正常。Android TV functional;YouTube & Play Movies compatibility & performance
3.2 通过报告定位到测试源码
3.2.1 device端的apk形式测试用例
大部分的测试用例均是该种形式,组织为一个测试apk,推送到Android设备运行并返回测试结果。他们的源码定位方式如下。
以第二节中camera项为例查找对应tag的代码。此处推荐两个网站
https://android.googlesource.com
与
https://cs.android.com
。一般是在cs.android上搜测试类名方法名,找到路径后去googlesource查找对应tag的精确的代码。
或者自己维护一个aosp的工程,随时到子库中切换至对应分支tag。
CtsCameraTestCases
是模块名,往往构建为测试套件中的一个apk或jar包。见测试套件目录:
./android-cts/testcases/CtsCameraTestCases.apk
./android-cts/testcases/CtsCameraTestCases.config```
定位异常行。
java写的测试用例会有异常调用栈的打印。在对应测试结果与日志目录下俩文件,logs/2020.11.10_15.03.26/inv_xxx/host_log_xxx.txt
与results/2020.11.10_15.03.26/test_result.xml
<TestCase name="android.hardware.camera2.cts.CameraManagerTest">
<Test result="pass" name="testCameraManagerGetCameraCharacteristics" />
<Test result="fail" name="testCameraManagerGetDeviceIdList">
<Failure message="junit.framework.AssertionFailedError: External camera is not connected on device with FEATURE_CAMERA_EXTERNAL ">
<StackTrace>junit.framework.AssertionFailedError: External camera is not connected on device with FEATURE_CAMERA_EXTERNAL
at junit.framework.Assert.fail(Assert.java:50)
at junit.framework.Assert.assertTrue(Assert.java:20)
at android.hardware.camera2.cts.CameraManagerTest.testCameraManagerGetDeviceIdList(CameraManagerTest.java:172)
at java.lang.reflect.Method.invoke(Native Method)
at junit.framework.TestCase.runTest(TestCase.java:168)
at junit.framework.TestCase.runBare(TestCase.java:134)
at junit.framework.TestResult$1.protect(TestResult.java:115)
at androidx.test.internal.runner.junit3.AndroidTestResult.runProtected(AndroidTestResult.java:73)
at junit.framework.TestResult.run(TestResult.java:118)
at androidx.test.internal.runner.junit3.AndroidTestResult.run(AndroidTestResult.java:51)
at junit.framework.TestCase.run(TestCase.java:124)
at androidx.test.internal.runner.junit3.NonLeakyTestSuite$NonLeakyTest.run(NonLeakyTestSuite.java:62)
at androidx.test.internal.runner.junit3.AndroidTestSuite$2.run(AndroidTestSuite.java:101)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
关于GTS、TVTS我们如何处置?由于不开源,所以只能去测试套件下反编译响应模块的apk或jar来阅读测试代码。我一般是jadx-gui拖进去即可,有跳转有行号。
3.2.2 host端的测试用例
典型的是部分vts测试用例。我们看下面这条测试。
armeabi-v7a VtsKernelProcFileApi
确认下模块名**VtsKernelProcFileApi**
```test/vts-testcase/kernel/api/proc$ ag VtsKernelProcFileApi
Android.bp
18: name: "VtsKernelProcFileApi",
AndroidTest.xml
26: <option name="test-module-name" value="VtsKernelProcFileApi" />
27: <option name="test-case-path" value="vts/testcases/kernel/api/proc/VtsKernelProcFileApiTest" />
VtsKernelProcFileApiTest.py
123:class VtsKernelProcFileApiTest(base_test.BaseTestClass):
定位测试方法
ag ProcMemInfoTest
ProcMemInfoTest.py
35:class ProcMemInfoTest(KernelProcFileTestBase.KernelProcFileTestBase):`
VtsKernelProcFileApiTest.py
32:from vts.testcases.kernel.api.proc import ProcMemInfoTest
63: ProcMemInfoTest.ProcMemInfoTest(),
定位crash异常代码行
test/vts-testcase/kernel/api/proc$ ag "Failed to parse line"
KernelProcFileTestBase.py
159: raise SyntaxError("Failed to parse line %s according to rule %s" %
和3.2.1中情况不同,由于测试用例不是device端,没有crash调用栈,所以通过搜索来确认。
四、 几个分析案例
现在,我们有了报告和日志、可以100%复现的场景与设备,并且拥有了程序源码。已经没有什么能阻挡我们分析bug了。之后的流程与其他Android的bug分析,或者说,和计算机编程领域的问题分析是完全一致的。
接下来,我们进行几个xts失败项的分析,如此来对xts分析有个感性的印象。
4.1 cts
Suite / Plan CTS / cts
Suite / Build 10_r5 / 6723298
先说结论。
这是Q版本上的一条失败项,失败的原因是bsp需求开发占用了响应的gpio,导致consumerIr这个红外发射功能,驱动是没有实现的。测试用例调用响应接口去发射红外,然后期望一段时间有结果。实际花费时间要在期望值的0.5倍到1.5倍之间。由于我们驱动是空实现,所以直接异常返回导致用时非常短。测试失败。
解决方案。
解决方案明显的,有两条路,我们不需要红外发射功能,所以可以去掉,我们采用这项;另一条就是底层真正实现它了。
测试代码:cts/tests/tests/hardware/src/android/hardware/consumerir/cts/ConsumerIrTest.java
public void test_timing() {
if (!mHasConsumerIr) {
// Skip the test if consumer IR is not present.
return;
ConsumerIrManager.CarrierFrequencyRange[] freqs = mCIR.getCarrierFrequencies();
// Transmit two seconds for min and max for each frequency range
int[] pattern = {11111, 22222, 33333, 44444, 55555, 66666, 77777, 88888, 99999};
long totalXmitTimeNanos = 0; // get the length of the pattern
for (int slice : pattern) {
totalXmitTimeNanos += slice * 1000; // add the time in nanoseconds
double margin = 0.5; // max fraction xmit is allowed to be off timing
for (ConsumerIrManager.CarrierFrequencyRange range : freqs) {
// test min freq
long currentTime = SystemClock.elapsedRealtimeNanos();
mCIR.transmit(range.getMinFrequency(), pattern);
long newTime = SystemClock.elapsedRealtimeNanos();
String msg = String.format("Pattern length pattern:%d, actual:%d",
totalXmitTimeNanos, newTime - currentTime);
assertTrue(msg, newTime - currentTime >= totalXmitTimeNanos * (1.0 - margin));
assertTrue(msg, newTime - currentTime <= totalXmitTimeNanos * (1.0 + margin));
// test max freq
currentTime = SystemClock.elapsedRealtimeNanos();
mCIR.transmit(range.getMaxFrequency(), pattern);
newTime = SystemClock.elapsedRealtimeNanos();
msg = String.format("Pattern length pattern:%d, actual:%d",
totalXmitTimeNanos, newTime - currentTime);
assertTrue(msg, newTime - currentTime >= totalXmitTimeNanos * (1.0 - margin));
assertTrue(msg, newTime - currentTime <= totalXmitTimeNanos * (1.0 + margin));//1
如上是测试代码,在注释//1
处断言抛异常,说明实际时间太小了。到底为什么小呢?ConsumerIrManager#transmit。我们往下跟这个方法。
* Transmit an infrared pattern
* This method is synchronous; when it returns the pattern has
* been transmitted. Only patterns shorter than 2 seconds will
* be transmitted.
* @param carrierFrequency The IR carrier frequency in Hertz.
* @param pattern The alternating on/off pattern in microseconds to transmit.
public void transmit(int carrierFrequency, int[] pattern) {
if (mService == null) {
Log.w(TAG, "failed to transmit; no consumer ir service.");
return;
try {
mService.transmit(mPackageName, carrierFrequency, pattern);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
字面意思,接收指定的频率和模式来发射红外。继续去服务实现里看
frameworks/base/services/core/java/com/android/server/ConsumerIrService.java
@Override
public void transmit(String packageName, int carrierFrequency, int[] pattern) {
// Right now there is no mechanism to ensure fair queing of IR requests
synchronized (mHalLock) {
int err = halTransmit(carrierFrequency, pattern);//1
注释//1
这里开始进入c层代码,通过jni走进hal层然后沟通驱动里的实现。
frameworks/base/services/core/jni/com_android_server_ConsumerIrService.cpp
#include <android/hardware/ir/1.0/IConsumerIr.h>
#include <nativehelper/ScopedPrimitiveArray.h>
using ::android::hardware::ir::V1_0::IConsumerIr;
using ::android::hardware::ir::V1_0::ConsumerIrFreqRange;
using ::android::hardware::hidl_vec;
namespace android {
static sp<IConsumerIr> mHal;
static jboolean halOpen(JNIEnv* /* env */, jobject /* obj */) {
// TODO(b/31632518)
mHal = IConsumerIr::getService();
return mHal != nullptr;
static jint halTransmit(JNIEnv *env, jobject /* obj */, jint carrierFrequency,
jintArray pattern) {
ScopedIntArrayRO cPattern(env, pattern);
if (cPattern.get() == NULL) {
return -EINVAL;
hidl_vec<int32_t> patternVec;
patternVec.setToExternal(const_cast<int32_t*>(cPattern.get()), cPattern.size());
bool success = mHal->transmit(carrierFrequency, patternVec);//1
return success ? 0 : -1;
到这里要去找hal的具体实现,需要了解hal层实现规则。可以扩展学习下Android Treble架构解析
穿越hal层,最终跟到hardware目录下的驱动实现,结合日志发现写节点失败了
hardware/xxx/consumerir/consumerir.cpp
static int consumerir_transmit(struct consumerir_device *dev __unused,
int carrier_freq, const int pattern[], int pattern_len) {
writeSys(IR_xxx_SEND, pPatterns, strlen(pPatterns));
10-02 07:20:49.706 3273 3273 E ConsumerIrHal: writeSysFs, open /sys/devices/virtual/irblaster/xxx/send fail. No such file or directory
从框架角度开看问题的话,至此可以下结论了。原来失败是因为驱动实现异常,节点不存在,根本没去发射所以耗时很短。转交了bsp去解决。后来bsp的动作是去掉了框架里FEATURE_CONSUMER_IR。但是这也埋下了vts另一个问题的伏笔。就是接下来的4.2小结。
4.2 vts
Suite / Plan VTS / vts
Suite / Build 10_r5 / 6719887
先说结论。
4.1节提到的cts失败项,去除feature时,仅将feature配置android.hardware.consumerir.xml
和vendor/etc/vintf/manifest.xml
中hidl配置删除。
<hal format="hidl">
<name>android.hardware.ir</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IConsumerIr</name>
<instance>default</instance>
</interface>
<fqname>@1.0::IConsumerIr/default</fqname>
没有进一步删除init.rc中的服务。如此导致虽没有hal声明,但是服务进程仍会开机启动。
该项测试即要求hal的声明与启动的服务保持一致:SystemVendorTest.ServedHwbinderHalsAreInManifest_32bit
$ ps -e |grep ir
system 3357 1 11020 3912 0 0 S android.hardware.ir@1.0-service
$ logcat |grep -i 3357
01-01 00:00:10.747 3357 3357 I ConsumerIrHal: consumerir hal open success
01-01 00:00:10.753 3357 3357 I ServiceManagement: Registered android.hardware.ir@1.0::IConsumerIr/default (start delay of 207ms)
01-01 00:00:10.753 3357 3357 I ServiceManagement: Removing namespace from process name android.hardware.ir@1.0-service to ir@1.0-service.
01-01 00:00:10.754 3357 3357 I android.hardware.ir@1.0-service: Registration complete for android.hardware.ir@1.0::IConsumerIr/default.
送佛送到西,把ConsumerIr的HAL层及以下代码全部送走。
$ git diff
diff --git a/products/xxx/xxx.mk b/products/xxx/xxx.mk
index be77805..9cdab74 100644
--- a/products/xxx/xxx.mk
+++ b/products/xxx/xxx.mk
@@ -237,16 +237,19 @@ endif
# ConsumerIr
#########################################################################
-PRODUCT_PACKAGES += \
+#---20201107---
+#delete ConsumerIr service for cts/vts
+#PRODUCT_PACKAGES += \
consumerir.app
#PRODUCT_COPY_FILES += \
# frameworks/native/data/etc/android.hardware.consumerir.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.consumerir.xml
#consumerir hal
-PRODUCT_PACKAGES += \
- android.hardware.ir@1.0-impl \
- android.hardware.ir@1.0-service
+#PRODUCT_PACKAGES += \
+# android.hardware.ir@1.0-impl \
+# android.hardware.ir@1.0-service
测试代码:test/vts-testcase/hal/treble/vintf/SystemVendorTest.cpp
// This needs to be tested besides
// SingleManifestTest.ServedHwbinderHalsAreInManifest because some HALs may
// refuse to provide its PID, and the partition cannot be inferred.
TEST_F(SystemVendorTest, ServedHwbinderHalsAreInManifest) {
auto device_manifest = VintfObject::GetDeviceHalManifest();
ASSERT_NE(device_manifest, nullptr) << "Failed to get device HAL manifest.";
auto fwk_manifest = VintfObject::GetFrameworkHalManifest();
ASSERT_NE(fwk_manifest, nullptr) << "Failed to get framework HAL manifest.";
std::set<std::string> manifest_hwbinder_hals;
insert(&manifest_hwbinder_hals, GetHwbinderHals(fwk_manifest));
insert(&manifest_hwbinder_hals, GetHwbinderHals(device_manifest));
Return<void> ret = default_manager_->list([&](const auto &list) {
for (const auto &name : list) {
// TODO(b/73774955): use standardized parsing code for fqinstancename
if (std::string(name).find(IBase::descriptor) == 0) continue;
EXPECT_NE(manifest_hwbinder_hals.find(name), manifest_hwbinder_hals.end())
<< name << " is being served, but it is not in a manifest.";//1
EXPECT_TRUE(ret.isOk());
注释//1
处断言抛异常,字面意思,hal实体服务启动后在serviceManaegr里已注册,但是manifest.xml里未作声明。这段代码涉及init进程启动rc里声明的服务、serviceManager的服务注册以及hidl服务的注册。涉及的东西很经典,逻辑很清晰。
有同学看到这可能要问了。代码我看了,为什么这么麻烦,刚开始4.1那条只删除feature不就没这么多事儿了?
确实这是个很好的思路,第一点想到这里,是很正确的方向。但是尝试后发现,system_server进程会crash,因为启动阶段有代码限制了这样做。所以不可以仅仅删除feature。
frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
if (!isWatch) {
t.traceBegin("StartConsumerIrService");
consumerIr = new ConsumerIrService(context);
ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr);
t.traceEnd();
frameworks/base/services/core/java/com/android/server/ConsumerIrService.java
ConsumerIrService(Context context) {
mContext = context;
PowerManager pm = (PowerManager)context.getSystemService(
Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mWakeLock.setReferenceCounted(true);
mHasNativeHal = halOpen();//1
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) {
if (!mHasNativeHal) {
throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!");
} else if (mHasNativeHal) {
throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!");
注释//1
之后的代码做了双向限制,保证hal与feature同在。否则system_server进程会一直crash,最终触发Rescure party机制。
4.3 gts
Suite / Plan GTS / gts
Suite / Build 7.0_r3 / 6045416
com.google.android.security.gts.SELinuxHostTest#testNoExemptionsForSocketsBetweenCoreAndVendorBan
junit.framework.AssertionFailedError: Policy exempts domains from ban on socket communications between core and vendor: [hal_audio_default]
at junit.framework.Assert.fail(Assert.java:57)
at junit.framework.TestCase.fail(TestCase.java:227)
at com.google.android.security.gts.SELinuxHostTest.testNoExemptionsForSocketsBetweenCoreAndVendorBan(SELinuxHostTest.java:221)
分析详细。
测试用例的逻辑:
使用linux可执行程序:sepolicy-analyze,对机顶盒中的/sys/fs/selinux/policy文件进行解析,要求不能有返回值,命令是:
sepolicy-analyze policy attribute socket_between_core_and_vendor_violators
即:不允许有type(类型)与该attribute(属性)“socket_between_core_and_vendor_violators”有关联,字面意思:core与vendor的违规socket特权。
分析测试代码:反编译后定位测试项
./com/google/android/security/gts/SELinuxHostTest.java
public void testNoExemptionsForVendorExecutingCore() throws Exception {
if (isFullTrebleDevice()) {
Set<String> types = sepolicyAnalyzeGetTypesAssociatedWithAttribute("vendor_executes_system_violators");//1
if (!types.isEmpty()) {
List<String> sortedTypes = new ArrayList(types);
Collections.sort(sortedTypes);
fail("Policy exempts vendor domains from ban on executing files in /system: " + sortedTypes);//
注释//1
:进去继续确认测试逻辑:sepolicyAnalyzeGetTypesAssociatedWithAttribute()
注释//2
:此处assert,原因是容器types有东西,东西就是‘[hal_audio_default]’
private Set<String> sepolicyAnalyzeGetTypesAssociatedWithAttribute(String attribute) throws Exception {
BufferedReader in;
Throwable th;
Throwable th2;
Set<String> types = new HashSet();
ProcessBuilder pb = new ProcessBuilder(new String[]{this.mSepolicyAnalyze.getAbsolutePath(), this.mDevicePolicyFile.getAbsolutePath(), "attribute", attribute});//1
......
in = new BufferedReader(new InputStreamReader(p.getInputStream()));
th = null;
while (true) {
try {
String type = in.readLine();
if (type != null) {
types.add(type.trim());//2
......
return types;
......
注释//1
:通过ProcessBuilder开启一个进程,用于执行linux命令:sepolicy-analyze policy attribute socket_between_core_and_vendor_violators
注释//2
:获取有效标准输出,写到结果容器中存储
现在基本逻辑就清楚了,只要这个命令执行有结果返回就是不被允许的,现在需要分析这个工具‘sepolicy-analyze’是干嘛的?
在Android工程源码中搜索,我们找到了这个host可执行程序的源码
system/sepolicy/tools/sepolicy-analyze/
结合网络资料以及阅读源码和README文档,澄清测试的命令用途:解析policy文件返回与attribute相关联的type值
工程中搜索确认到底在哪里使得他们关联的,定位到文件
./system/sepolicy/vendor/hal_audio_default.te:1
type hal_audio_default, domain, socket_between_core_and_vendor_violators;
查证git log,我们发现是如下的commit导致的,是google的auto-path,后续澄清为waiver项。
commit 1234567
Author: xxxxxx
Date: Mon Feb 17 11:33:16 2020 +0800
auto patch added:CecAudio
diff --git a/vendor/hal_audio_default.te b/vendor/hal_audio_default.te
index 0dc2170..9da0f1b 100644
--- a/vendor/hal_audio_default.te
+++ b/vendor/hal_audio_default.te
@@ -1,4 +1,4 @@
-type hal_audio_default, domain;
+type hal_audio_default, domain, socket_between_core_and_vendor_violators; #此处添加的关联,问题找到了根源
hal_server_domain(hal_audio_default, hal_audio)
相关资料:
system/sepolicy/tools/sepolicy-analyze/README
ATTRIBUTE (attribute)
sepolicy-analyze out/target/product//root/sepolicy attribute
Displays the types associated with the specified attribute name.
该权限详细限制在以下代码中有论述,Android TREBLE架构相关
system/sepolicy/prebuilts/api/26.0/public/domain.te system/sepolicy/prebuilts/api/27.0/public/domain.te system/sepolicy/prebuilts/api/28.0/public/domain.te: system/sepolicy/public/domain.te
# On full TREBLE devices, socket communications between core components and vendor components are
# not permitted.
full_treble_only(`
# Most general rules first, more specific rules below.
# Core domains are not permitted to initiate communications to vendor domain sockets.
# We are not restricting the use of already established sockets because it is fine for a process
# to obtain an already established socket via some public/official/stable API and then exchange
# data with its peer over that socket. The wire format in this scenario is dicatated by the API
# and thus does not break the core-vendor separation.
4.4 cts verifier
CtsVerifier 9.0-r11
失败项:Bluetooth Test --> Bluetooth LE Secure Client Test --> 01 BlueTooth LE Client Test
先说结论。
蓝牙驱动修改引入,转交bsp修复。
该项测试流程概述:
Client测试pass后做出先关闭mAdapter.disable()
后打开mAdapter.enable()
的动作,经梳理蓝牙框流程发现,蓝牙关闭后再次打开超时失败,由此导致该测试Activity无法收到广播,无法将按钮设置为可选。
分析详细。
1.测试Activity,提供ui无业务代码
cts/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleSecureClientStartActivity.java
2.上面(1)的父类,测试结果处理
cts/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
3.测试Service,真正的测试项执行
cts/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
1.启动BleSecureClientStartActivity
。该测试页面只是一个activity-ui。主要的方法实现和流程动作都在父类Activity中,重点关注父类。
2.调用父类BleClientTestBaseActivity的onCreate()
完成:
页面显示、设置底部pass-fail-button按键的监听、设置pass-button为disable不可选、
初始化页面的测试项ListView
回到子类继续onCreate():显示info提示信息的dialog、启动BleClientService
准备测试
3.调用父类BleClientTestBaseActivity的onResume()
:
注册测试服务BleClientService
的广播监听BroadcastReceiver mBroadcast
4.开始测试
这个测试是俩设备Server-Client配对测试,自动触发,细节略,和Server端之间的蓝牙通信有关
listview每一条测试结束都会有广播发出,接收广播后将mPassed做或运算,如果一切顺利mPassed的运算结果是PASS_FLAG_ALL
这代表测试项全部通过
private static final int PASS_FLAG_ALL = 0x3FFFF;
然后Client端将蓝牙关闭mAdapter.disable()
再打开mAdapter.enable()
,打开成功情况下Activity才会将Pass-Button设置为可选择
由于驱动代码出问题,遂enable()失败,无法设置按钮可选
测试All-PASS
04-21 16:52:05.901 D/BluetoothGatt( 6338): onClientConnectionState() - status=0 clientIf=6 device=7B:D0:42:AC:47:B6
04-21 16:52:05.901 D/BleClientService( 6338): onConnectionStateChange: status= 0, newState= 0
04-21 16:52:05.901 D/BluetoothGatt( 6338): close()
04-21 16:52:05.901 D/BluetoothGatt( 6338): unregisterApp() - mClientIf=6
04-21 16:52:05.915 D/BleClientTestBase( 6338): Processing com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_DISCONNECTED
04-21 16:52:05.921 D/BleClientTestBase( 6338): Passed Flags has changed from 0x0003FDFF to 0x0003FFFF. Delta=0x00000200
04-21 16:52:05.921 D/BleClientTestBase( 6338): All Tests Passed.
蓝牙正常关闭
04-21 16:52:06.931 D/AdapterProperties( 3406): Setting state to TURNING_OFF
04-21 16:52:07.038 I/AdapterState( 3406): BLE_TURNING_OFF : entered
04-21 16:52:07.057 I/bt_btif_core( 3406): btif_disable_bluetooth finished
蓝牙开启超时
04-21 16:52:17.051 D/BluetoothManagerService( 3297): enable(com.android.cts.verifier): mBluetooth =null mBinding = false mState = OFF
04-21 16:52:17.288 D/BluetoothAdapterService( 7058): bleOnProcessStart()
04-21 16:52:17.290 D/BluetoothManagerService( 3297): MESSAGE_BLUETOOTH_STATE_CHANGE: OFF > BLE_TURNING_ON
04-21 16:52:17.290 D/BluetoothManagerService( 3297): Sending BLE State Change: OFF > BLE_TURNING_ON
......
04-21 16:52:21.292 E/AdapterState( 7058): BLE_TURNING_ON : BLE_START_TIMEOUT
04-21 16:52:21.293 I/AdapterState( 7058): BLE_TURNING_OFF : entered
五、经验之谈
失败项到手,先确认他的来源。新工具引入?上个版本有吗?最近有什么修改?这些信息对缩小范围很重要。
xts测试是很多且杂的,一个人去掌控全局个人感觉会很吃力。所以有不熟悉的模块问题,要分发给对应模块同学分析。例如媒体、网络、驱动等等。
善用搜索引擎,获取直接或者间接的答案与知识。
对比和二分法是你最后的依靠。如果你没有头绪或者进展缓慢,时间紧迫时就回退版本吧。众所周知二分法的时间复杂度是O(lgn)。
修改验证时,了解编译框架会使你事半功倍。仅替换apk,so还是某个文件。或者刷个img镜像也比编译OTA包快的多。
六、一些资料、工具
Compatibility Test Suite
Vendor Test Suite (VTS) & Infrastructure
ATV Help#Android TV Certification
Android Code Search
Google Git