Merge "Improve CTS coverage for pointer capture API"
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ce1acbc..9fa9a7c 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -72,6 +72,8 @@
android:largeHeap="true"
android:theme="@android:style/Theme.DeviceDefault">
+ <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
+
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIbK6ldcOzoeRtQ1u1dFVJ1A7KetRhit-a1Xa82Q" />
<meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -147,14 +149,15 @@
android:value="android.software.backup" />
</activity>
- <activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_test">
+ <!-- Further work is required for this test, b/32798562 -->
+ <!-- activity android:name=".backup.BackupAccessibilityTestActivity" android:label="@string/backup_accessibility_test">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.cts.intent.category.MANUAL_TEST" />
</intent-filter>
<meta-data android:name="test_required_features"
android:value="android.software.backup" />
- </activity>
+ </activity -->
<!-- CTS Verifier Bluetooth Test Top Screen -->
<activity
@@ -2721,6 +2724,46 @@
<meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
</activity>
+ <activity android:name=".audio.USBAudioPeripheralAttributesActivity"
+ android:label="@string/audio_uap_attribs_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+ </activity>
+
+ <activity android:name=".audio.USBAudioPeripheralPlayActivity"
+ android:label="@string/audio_uap_play_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+ </activity>
+
+ <activity android:name=".audio.USBAudioPeripheralRecordActivity"
+ android:label="@string/audio_uap_record_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+ </activity>
+
+ <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
+ android:label="@string/audio_uap_buttons_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
+ </activity>
+
<activity android:name=".audio.AudioLoopbackActivity"
android:label="@string/audio_loopback_test">
<intent-filter>
@@ -2776,6 +2819,7 @@
</intent-filter>
<meta-data android:name="test_category" android:value="@string/test_category_audio" />
<meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
</activity>
<service android:name=".tv.MockTvInputService"
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 2350085..1e43211 100644
--- a/apps/CtsVerifier/jni/verifier/Android.mk
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -23,8 +23,7 @@
LOCAL_SRC_FILES := \
CtsVerifierJniOnLoad.cpp \
- com_android_cts_verifier_camera_StatsImage.cpp \
- com_android_cts_verifier_os_FileUtils.cpp
+ com_android_cts_verifier_camera_StatsImage.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
diff --git a/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp b/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
index 399275b..158fca4 100644
--- a/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
+++ b/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
@@ -17,7 +17,6 @@
#include <jni.h>
#include <stdio.h>
-extern int register_com_android_cts_verifier_os_FileUtils(JNIEnv*);
extern int register_com_android_cts_verifier_camera_its_StatsImage(JNIEnv*);
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
@@ -27,10 +26,6 @@
return JNI_ERR;
}
- if (register_com_android_cts_verifier_os_FileUtils(env)) {
- return JNI_ERR;
- }
-
if (register_com_android_cts_verifier_camera_its_StatsImage(env)) {
return JNI_ERR;
}
diff --git a/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp b/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
deleted file mode 100644
index 9e204e2..0000000
--- a/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <jni.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <assert.h>
-#include <grp.h>
-#include <pwd.h>
-#include <unistd.h>
-
-static jfieldID gFileStatusDevFieldID;
-static jfieldID gFileStatusInoFieldID;
-static jfieldID gFileStatusModeFieldID;
-static jfieldID gFileStatusNlinkFieldID;
-static jfieldID gFileStatusUidFieldID;
-static jfieldID gFileStatusGidFieldID;
-static jfieldID gFileStatusSizeFieldID;
-static jfieldID gFileStatusBlksizeFieldID;
-static jfieldID gFileStatusBlocksFieldID;
-static jfieldID gFileStatusAtimeFieldID;
-static jfieldID gFileStatusMtimeFieldID;
-static jfieldID gFileStatusCtimeFieldID;
-static jfieldID gFileStatusExecutableID;
-
-/* Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp */
-jboolean com_android_cts_verifier_os_FileUtils_getFileStatus(JNIEnv* env, jobject thiz,
- jstring path, jobject fileStatus, jboolean statLinks)
-{
- const char* pathStr = env->GetStringUTFChars(path, NULL);
- jboolean ret = false;
- struct stat s;
-
- int res = statLinks == true ? lstat(pathStr, &s) : stat(pathStr, &s);
-
- if (res == 0) {
- ret = true;
- if (fileStatus != NULL) {
- env->SetIntField(fileStatus, gFileStatusDevFieldID, s.st_dev);
- env->SetIntField(fileStatus, gFileStatusInoFieldID, s.st_ino);
- env->SetIntField(fileStatus, gFileStatusModeFieldID, s.st_mode);
- env->SetIntField(fileStatus, gFileStatusNlinkFieldID, s.st_nlink);
- env->SetIntField(fileStatus, gFileStatusUidFieldID, s.st_uid);
- env->SetIntField(fileStatus, gFileStatusGidFieldID, s.st_gid);
- env->SetLongField(fileStatus, gFileStatusSizeFieldID, s.st_size);
- env->SetIntField(fileStatus, gFileStatusBlksizeFieldID, s.st_blksize);
- env->SetLongField(fileStatus, gFileStatusBlocksFieldID, s.st_blocks);
- env->SetLongField(fileStatus, gFileStatusAtimeFieldID, s.st_atime);
- env->SetLongField(fileStatus, gFileStatusMtimeFieldID, s.st_mtime);
- env->SetLongField(fileStatus, gFileStatusCtimeFieldID, s.st_ctime);
- }
- if (access(pathStr, X_OK) == 0) {
- env->SetBooleanField(fileStatus, gFileStatusExecutableID, JNI_TRUE);
- } else {
- env->SetBooleanField(fileStatus, gFileStatusExecutableID, JNI_FALSE);
- }
- }
-
- env->ReleaseStringUTFChars(path, pathStr);
-
- return ret;
-}
-
-jstring com_android_cts_verifier_os_FileUtils_getUserName(JNIEnv* env, jobject thiz,
- jint uid)
-{
- struct passwd *pwd = getpwuid(uid);
- return env->NewStringUTF(pwd->pw_name);
-}
-
-jstring com_android_cts_verifier_os_FileUtils_getGroupName(JNIEnv* env, jobject thiz,
- jint gid)
-{
- struct group *grp = getgrgid(gid);
- return env->NewStringUTF(grp->gr_name);
-}
-
-static JNINativeMethod gMethods[] = {
- { "getFileStatus", "(Ljava/lang/String;Lcom/android/cts/verifier/os/FileUtils$FileStatus;Z)Z",
- (void *) com_android_cts_verifier_os_FileUtils_getFileStatus },
- { "getUserName", "(I)Ljava/lang/String;",
- (void *) com_android_cts_verifier_os_FileUtils_getUserName },
- { "getGroupName", "(I)Ljava/lang/String;",
- (void *) com_android_cts_verifier_os_FileUtils_getGroupName },
-};
-
-int register_com_android_cts_verifier_os_FileUtils(JNIEnv* env)
-{
- jclass clazz = env->FindClass("com/android/cts/verifier/os/FileUtils");
- assert(clazz != null);
-
- jclass fileStatusClass = env->FindClass("com/android/cts/verifier/os/FileUtils$FileStatus");
- assert(fileStatusClass != null);
- gFileStatusDevFieldID = env->GetFieldID(fileStatusClass, "dev", "I");
- gFileStatusInoFieldID = env->GetFieldID(fileStatusClass, "ino", "I");
- gFileStatusModeFieldID = env->GetFieldID(fileStatusClass, "mode", "I");
- gFileStatusNlinkFieldID = env->GetFieldID(fileStatusClass, "nlink", "I");
- gFileStatusUidFieldID = env->GetFieldID(fileStatusClass, "uid", "I");
- gFileStatusGidFieldID = env->GetFieldID(fileStatusClass, "gid", "I");
- gFileStatusSizeFieldID = env->GetFieldID(fileStatusClass, "size", "J");
- gFileStatusBlksizeFieldID = env->GetFieldID(fileStatusClass, "blksize", "I");
- gFileStatusBlocksFieldID = env->GetFieldID(fileStatusClass, "blocks", "J");
- gFileStatusAtimeFieldID = env->GetFieldID(fileStatusClass, "atime", "J");
- gFileStatusMtimeFieldID = env->GetFieldID(fileStatusClass, "mtime", "J");
- gFileStatusCtimeFieldID = env->GetFieldID(fileStatusClass, "ctime", "J");
- gFileStatusExecutableID = env->GetFieldID(fileStatusClass, "executable", "Z");
-
- return env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(JNINativeMethod));
-}
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index 9f7ffcd..a28ee56 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -2,10 +2,6 @@
native <methods>;
}
--keepclassmembers class com.android.cts.verifier.os.FileUtils$FileStatus {
- private <fields>;
-}
-
# ensure we keep public sensor test methods, these are needed at runtime
-keepclassmembers class * extends com.android.cts.verifier.sensors.base.BaseSensorTestActivity {
public <methods>;
diff --git a/apps/CtsVerifier/res/layout/uap_attribs_panel.xml b/apps/CtsVerifier/res/layout/uap_attribs_panel.xml
new file mode 100644
index 0000000..ea4e719
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_attribs_panel.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView"
+ style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/uap_profile_header"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+ <TextView
+ android:text="@string/uapPeripheralProfileStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:text="status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_attribsStatusTx"
+ android:paddingLeft="16dp"/>
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_buttons_panel.xml b/apps/CtsVerifier/res/layout/uap_buttons_panel.xml
new file mode 100644
index 0000000..2aeaca2
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_buttons_panel.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView"
+ style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/uap_profile_header"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:text="@string/uapButtonTestInstructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:text="@string/uapButtonsBtnALbl"
+ android:id="@+id/uap_buttonsBtnALabelTx"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:text="@string/uapButtonsNotRecognized"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_buttonsBtnAStatusTx"
+ android:paddingLeft="16dp"/>
+
+ <TextView
+ android:text="@string/uapButtonsBtnBLbl"
+ android:id="@+id/uap_buttonsBtnBLabelTx"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:text="@string/uapButtonsNotRecognized"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_buttonsBtnBStatusTx"
+ android:paddingLeft="16dp"/>
+
+ <TextView
+ android:text="@string/uapButtonsBtnCLbl"
+ android:id="@+id/uap_buttonsBtnCLabelTx"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:text="@string/uapButtonsNotRecognized"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_buttonsBtnCStatusTx"
+ android:paddingLeft="16dp"/>
+
+ <TextView
+ android:text="@string/uapButtonsBtnDLbl"
+ android:id="@+id/uap_buttonsBtnDLabelTx"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:text="@string/uapButtonsNotRecognized"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_buttonsBtnDStatusTx"
+ android:paddingLeft="16dp"/>
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_play_panel.xml b/apps/CtsVerifier/res/layout/uap_play_panel.xml
new file mode 100644
index 0000000..1cb469a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_play_panel.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView"
+ style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/uap_profile_header"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:text="@string/uapPlayTestInstructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:text="@string/audio_uap_play_playBtn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_playPlayBtn"
+ android:nextFocusUp="@+id/fail_button"
+ android:nextFocusDown="@+id/pass_button"/>
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons"/>
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_profile_header.xml b/apps/CtsVerifier/res/layout/uap_profile_header.xml
new file mode 100644
index 0000000..ca3af8f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_profile_header.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:text="@string/profileLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_profileNameTx"
+ android:paddingLeft="16dp"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_profileDescriptionTx"
+ android:paddingLeft="16dp"/>
+
+ <TextView
+ android:text="@string/connectedPeripheral"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_peripheralNameTx"
+ android:paddingLeft="16dp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/uap_record_panel.xml b/apps/CtsVerifier/res/layout/uap_record_panel.xml
new file mode 100644
index 0000000..5277389
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/uap_record_panel.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView"
+ style="@style/RootLayoutPadding">
+
+<LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/uap_profile_header"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:text="@string/uapRecordTestInstructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:text="@string/audio_uap_record_recordBtn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_recordRecordBtn"
+ android:nextFocusUp="@+id/fail_button"
+ android:nextFocusDown="@+id/uap_recordRecordLoopBtn"/>
+
+ <Button
+ android:text="@string/audio_uap_record_recordLoopbackBtn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/uap_recordRecordLoopBtn"
+ android:nextFocusUp="@+id/uap_recordRecordBtn"
+ android:nextFocusDown="@+id/pass_button"/>
+ </LinearLayout>
+
+ <com.android.cts.verifier.audio.audiolib.WaveScopeView
+ android:id="@+id/uap_recordWaveView"
+ android:layout_width="match_parent"
+ android:layout_height="256dp"/>
+
+ <include layout="@layout/pass_fail_buttons"/>
+
+</LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
old mode 100644
new mode 100755
index b522c61..ffbf945
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -414,8 +414,8 @@
<string name="ble_low">Low</string>
<string name="ble_medium">Medium</string>
<string name="ble_high">High</string>
- <string name="ble_scanner_power_level_instruction">Count: Ultra low < low < medium < high\nRssi: Ultra low < low < medium < high\nDistance to see count freezing: Ultra low < low < medium < high\nA common error is ultra low, low and medium behave similarly, with similar rssi, freeze at similar distance.\n\n All power level receive a different mac address. After 15 mins, a green text "Get a new Mac address" will show up.</string>
- <string name="ble_scanner_scan_filter_name">Bluetooth LE Hardware Scan Filter</string>
+ <string name="ble_scanner_power_level_instruction">Count: Ultra low < low < medium < high\nRssi: Ultra low < low < medium < high\nDistance to see count freezing: Ultra low < low < medium < high\nA common error is ultra low, low and medium behave similarly, with similar rssi, freeze at similar distance.\n\n All power level receive a mac address.</string>
+ <string name="ble_scanner_scan_filter_name">BLE Hardware Scan Filter</string>
<string name="ble_scanner_scan_filter_info">Lock the screen of scanner, and connect to monsoon. It will not wake up when advertiser is advertising unscannable, and scanner is scanning with filter.</string>
<string name="ble_scanner_scan_filter_instruction">Scan filter is to scan data with service UUID = 0x6666 only. If you scan without scan filter, data with service UUID = 0x5555 and 0x6666 will show up on screen.\nFor monsoon test:\n\tClick scan with filter, lock the screen, connect to monsoon. It will not wake up when advertiser is advertising unscannable data packets, but will show a peak in power usage when advertiser is advertising scannable data.\nFor logcat test:\n\tClick scan with filter, logcat the scanner. No data will be received by GattService when advertiser is advertising unscannable data.</string>
<string name="ble_scan_with_filter">Scan with filter</string>
@@ -3255,6 +3255,36 @@
<string name="audio_routingnotification_trackRoutingMsg">AudioTrack rerouting</string>
<string name="audio_routingnotification_recordRoutingMsg">AudioRecord rerouting</string>
+ <!-- USB Audio Peripheral Tests -->
+ <string name="profileLabel">Profile</string>
+ <string name="connectedPeripheral">Connected Peripheral</string>
+ <string name="audio_uap_attribs_test">USB Audio Peripheral Attributes Test</string>
+ <string name="uapPeripheralProfileStatus">Peripheral Profile Status</string>
+
+ <string name="audio_uap_play_test">USB Audio Peripheral Play Test</string>
+ <string name="uapPlayTestInstructions">Connect the USB Audio Peripheral and press the PLAY button below.
+ Verify that a tone is correctly played.</string>
+ <string name="audio_uap_play_playBtn">Play</string>
+ <string name="audio_uap_play_stopBtn">Stop</string>
+
+ <string name="audio_uap_record_test">USB Audio Peripheral Record Test</string>
+ <string name="uapRecordTestInstructions">Connect the USB Audio Peripheral and press the RECORD or
+ RECORD LOOPBACK button below. Verify that a tone is correctly played.</string>
+ <string name="audio_uap_record_recordBtn">Record</string>
+ <string name="audio_uap_record_recordLoopbackBtn">Record Loopback</string>
+ <string name="audio_uap_record_stopBtn">Stop</string>
+
+ <string name="audio_uap_buttons_test">USB Audio Peripheral Buttons Test</string>
+ <string name="uapButtonTestInstructions">Connect the USB Audio headset with buttons
+ and press each transport/media button in turn.</string>
+
+ <string name="uapButtonsBtnALbl">Button A - play/pause</string>
+ <string name="uapButtonsBtnBLbl">Button B - volume up (+)</string>
+ <string name="uapButtonsBtnCLbl">Button C - volume down (-)</string>
+ <string name="uapButtonsBtnDLbl">Button D - voice assist</string>
+ <string name="uapButtonsRecognized">Recognized</string>
+ <string name="uapButtonsNotRecognized">Not Recognized</string>
+
<!-- Audio general text -->
<string name="audio_general_headset_port_exists">Does this device have a headset port?</string>
<string name="audio_general_headset_no">No</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 6a9c32f..1629e1b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -52,17 +52,17 @@
*/
class ReportExporter extends AsyncTask<Void, Void, String> {
- private static final String COMMAND_LINE_ARGS = "CtsVerifier";
+ private static final String COMMAND_LINE_ARGS = "";
private static final String LOG_URL = null;
private static final String REFERENCE_URL = null;
- private static final String SUITE_NAME = "CTS_VERIFIER";
- private static final String SUITE_PLAN = "CTSVERIFIER";
+ private static final String SUITE_NAME_METADATA_KEY = "SuiteName";
+ private static final String SUITE_PLAN = "verifier";
private static final String SUITE_BUILD = "0";
private static final long START_MS = System.currentTimeMillis();
private static final long END_MS = START_MS;
- private static final String REPORT_DIRECTORY = "ctsVerifierReports";
+ private static final String REPORT_DIRECTORY = "verifierReports";
private static final String ZIP_EXTENSION = ".zip";
protected static final Logger LOG = Logger.getLogger(ReportExporter.class.getName());
@@ -93,17 +93,20 @@
File externalStorageDirectory = Environment.getExternalStorageDirectory();
File verifierReportsDir = new File(externalStorageDirectory, REPORT_DIRECTORY);
verifierReportsDir.mkdirs();
+
+ String suiteName = Version.getMetadata(mContext, SUITE_NAME_METADATA_KEY);
// create a temporary directory for this particular report
- File tempDir = new File(verifierReportsDir, getReportName());
+ File tempDir = new File(verifierReportsDir, getReportName(suiteName));
tempDir.mkdirs();
// create a File object for a report ZIP file
- File reportZipFile = new File(verifierReportsDir, getReportName() + ZIP_EXTENSION);
+ File reportZipFile = new File(
+ verifierReportsDir, getReportName(suiteName) + ZIP_EXTENSION);
try {
// Serialize the report
String versionName = Version.getVersionName(mContext);
- ResultHandler.writeResults(SUITE_NAME, versionName, SUITE_PLAN, SUITE_BUILD,
+ ResultHandler.writeResults(suiteName, versionName, SUITE_PLAN, SUITE_BUILD,
result, tempDir, START_MS, END_MS, REFERENCE_URL, LOG_URL,
COMMAND_LINE_ARGS);
@@ -147,11 +150,11 @@
}
}
- private String getReportName() {
+ private String getReportName(String suiteName) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.ENGLISH);
String date = dateFormat.format(new Date());
- return String.format( "%s-%s-%s-%s-%s",
- date, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID);
+ return String.format( "%s-%s-%s-%s-%s-%s",
+ date, suiteName, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID);
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
index e7b6121..272fbcd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/Version.java
@@ -17,12 +17,18 @@
package com.android.cts.verifier;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
class Version {
+ private static final String TAG = Version.class.getSimpleName();
+
+ private static final String UNKNOWN = "unknown";
+
static String getVersionName(Context context) {
return getPackageInfo(context).versionName;
}
@@ -40,4 +46,19 @@
+ context.getPackageName());
}
}
+
+ static String getMetadata(Context context, String name) {
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
+ context.getPackageName(), PackageManager.GET_META_DATA);
+ String value = applicationInfo.metaData.getString(name);
+ if (value != null) {
+ return value;
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Version.getMetadata: " + name, e);
+ }
+ return UNKNOWN;
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
new file mode 100644
index 0000000..21a70b9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.ProfileManager;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
+
+public abstract class USBAudioPeripheralActivity extends PassFailButtons.Activity {
+ private static final String TAG = "USBAudioPeripheralActivity";
+
+ // Profile
+ protected ProfileManager mProfileManager = new ProfileManager();
+ protected PeripheralProfile mSelectedProfile;
+
+ // Peripheral
+ AudioManager mAudioManager;
+ protected boolean mIsPeripheralAttached;
+ protected AudioDeviceInfo mOutputDevInfo;
+ protected AudioDeviceInfo mInputDevInfo;
+
+ // This will be overriden...
+ protected int mSystemSampleRate = 48000;
+
+ // Widgets
+ private TextView mProfileNameTx;
+ private TextView mProfileDescriptionTx;
+
+ private TextView mPeripheralNameTx;
+
+ public USBAudioPeripheralActivity() {
+ super();
+
+ mProfileManager.loadProfiles();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+ mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+ mSystemSampleRate = Integer.parseInt(
+ mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+ }
+
+ protected void connectPeripheralStatusWidgets() {
+ mProfileNameTx = (TextView)findViewById(R.id.uap_profileNameTx);
+ mProfileDescriptionTx =
+ (TextView)findViewById(R.id.uap_profileDescriptionTx);
+ mPeripheralNameTx = (TextView)findViewById(R.id.uap_peripheralNameTx);
+ }
+
+ private void showProfileStatus() {
+ if (mSelectedProfile != null) {
+ mProfileNameTx.setText(mSelectedProfile.getName());
+ mProfileDescriptionTx.setText(mSelectedProfile.getDescription());
+ } else {
+ mProfileNameTx.setText("");
+ mProfileDescriptionTx.setText("");
+ }
+ }
+
+ private void showPeripheralStatus() {
+ if (mIsPeripheralAttached) {
+ if (mOutputDevInfo != null) {
+ mPeripheralNameTx.setText(mOutputDevInfo.getProductName().toString());
+ } else if (mInputDevInfo != null) {
+ mPeripheralNameTx.setText(mInputDevInfo.getProductName().toString());
+ }
+ } else {
+ mPeripheralNameTx.setText("Disconnected");
+ }
+ }
+
+ private void scanPeripheralList(AudioDeviceInfo[] devices) {
+ // Can't just use the first record because then we will only get
+ // Source OR sink, not both even on devices that are both.
+ mOutputDevInfo = null;
+ mInputDevInfo = null;
+
+ // Any valid peripherals
+ for(AudioDeviceInfo devInfo : devices) {
+ if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) {
+ if (devInfo.isSink()) {
+ mOutputDevInfo = devInfo;
+ }
+ if (devInfo.isSource()) {
+ mInputDevInfo = devInfo;
+ }
+ }
+ }
+ mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
+ // Log.i(TAG, "mIsPeripheralAttached: " + mIsPeripheralAttached);
+
+ // any associated profiles?
+ if (mIsPeripheralAttached) {
+ if (mOutputDevInfo != null) {
+ mSelectedProfile =
+ mProfileManager.getProfile(mOutputDevInfo.getProductName().toString());
+ } else if (mInputDevInfo != null) {
+ mSelectedProfile =
+ mProfileManager.getProfile(mInputDevInfo.getProductName().toString());
+ }
+ } else {
+ mSelectedProfile = null;
+ }
+
+ }
+
+ private class ConnectListener extends AudioDeviceCallback {
+ /*package*/ ConnectListener() {}
+
+ //
+ // AudioDevicesManager.OnDeviceConnectionListener
+ //
+ @Override
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ // Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+ scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+
+ showProfileStatus();
+ showPeripheralStatus();
+ updateConnectStatus();
+ }
+
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ // Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+ scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+
+ showProfileStatus();
+ showPeripheralStatus();
+ updateConnectStatus();
+ }
+ }
+
+ abstract public void updateConnectStatus();
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
new file mode 100644
index 0000000..f0c75c4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralAttributesActivity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.ListsHelper;
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+
+import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralAttributesActivity extends USBAudioPeripheralActivity {
+ private static final String TAG = "USBAudioPeripheralAttributesActivity";
+
+ private TextView mTestStatusTx;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.uap_attribs_panel);
+
+ connectPeripheralStatusWidgets();
+
+ mTestStatusTx = (TextView)findViewById(R.id.uap_attribsStatusTx);
+
+ setPassFailButtonClickListeners();
+ }
+
+ //
+ // USBAudioPeripheralActivity
+ //
+ public void updateConnectStatus() {
+ boolean outPass = false;
+ boolean inPass = false;
+ if (mIsPeripheralAttached && mSelectedProfile != null) {
+ boolean match = true;
+ StringBuilder metaSb = new StringBuilder();
+
+ // Outputs
+ if (mOutputDevInfo != null) {
+ AudioDeviceInfo deviceInfo = mOutputDevInfo;
+ PeripheralProfile.ProfileAttributes attribs =
+ mSelectedProfile.getOutputAttributes();
+ StringBuilder sb = new StringBuilder();
+
+ if (!ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
+ sb.append("Output - Channel Counts Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(),
+ attribs.mChannelIndexMasks)) {
+ sb.append("Output - Channel Index Masks Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getChannelMasks(),
+ attribs.mChannelPositionMasks)) {
+ sb.append("Output - Channel Position Masks Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings)) {
+ sb.append("Output - Encodings Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
+ sb.append("Output - Sample Rates Mismatch\n");
+ }
+
+ if (sb.toString().length() == 0){
+ metaSb.append("Output - Match\n");
+ outPass = true;
+ } else {
+ metaSb.append(sb.toString());
+ }
+ }
+
+ // Inputs
+ if (mInputDevInfo != null) {
+ AudioDeviceInfo deviceInfo = mInputDevInfo;
+ PeripheralProfile.ProfileAttributes attribs =
+ mSelectedProfile.getInputAttributes();
+ StringBuilder sb = new StringBuilder();
+
+ if (!ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts)) {
+ sb.append("Input - Channel Counts Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(),
+ attribs.mChannelIndexMasks)) {
+ sb.append("Input - Channel Index Masks Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getChannelMasks(),
+ attribs.mChannelPositionMasks)) {
+ sb.append("Input - Channel Position Masks Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings)) {
+ sb.append("Input - Encodings Mismatch\n");
+ }
+ if (!ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates)) {
+ sb.append("Input - Sample Rates Mismatch\n");
+ }
+
+ if (sb.toString().length() == 0){
+ inPass = true;
+ metaSb.append("Input - Match\n");
+ } else {
+ metaSb.append(sb.toString());
+ }
+ }
+
+ mTestStatusTx.setText(metaSb.toString());
+ } else {
+ mTestStatusTx.setText("No Peripheral or No Matching Profile.");
+ }
+
+ //TODO we need to support output-only and input-only peripherals
+ getPassButton().setEnabled(outPass && inPass);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
new file mode 100644
index 0000000..fbaf877
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralButtonsActivity.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.ProfileButtonAttributes;
+
+import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralButtonsActivity extends USBAudioPeripheralActivity {
+ private static final String TAG = "USBAudioPeripheralButtonsActivity";
+
+ // State
+ private boolean mHasBtnA;
+ private boolean mHasBtnB;
+ private boolean mHasBtnC;
+ private boolean mHasBtnD;
+
+ // Widgets
+ private TextView mBtnALabelTxt;
+ private TextView mBtnBLabelTxt;
+ private TextView mBtnCLabelTxt;
+ private TextView mBtnDLabelTxt;
+
+ private TextView mBtnAStatusTxt;
+ private TextView mBtnBStatusTxt;
+ private TextView mBtnCStatusTxt;
+ private TextView mBtnDStatusTxt;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.uap_buttons_panel);
+
+ connectPeripheralStatusWidgets();
+
+ mBtnALabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnALabelTx);
+ mBtnBLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnBLabelTx);
+ mBtnCLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnCLabelTx);
+ mBtnDLabelTxt = (TextView)findViewById(R.id.uap_buttonsBtnDLabelTx);
+
+ mBtnAStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnAStatusTx);
+ mBtnBStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnBStatusTx);
+ mBtnCStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnCStatusTx);
+ mBtnDStatusTxt = (TextView)findViewById(R.id.uap_buttonsBtnDStatusTx);
+
+ setPassFailButtonClickListeners();
+ }
+
+ private void showButtonsState() {
+ if (mIsPeripheralAttached && mSelectedProfile != null) {
+ ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
+ if (mButtonAttributes != null) {
+ if (!mButtonAttributes.mHasBtnA) {
+ mBtnALabelTxt.setTextColor(Color.GRAY);
+ mBtnAStatusTxt.setTextColor(Color.GRAY);
+ } else {
+ mBtnALabelTxt.setTextColor(Color.WHITE);
+ mBtnAStatusTxt.setTextColor(Color.WHITE);
+ }
+ if (!mButtonAttributes.mHasBtnB) {
+ mBtnBLabelTxt.setTextColor(Color.GRAY);
+ mBtnBStatusTxt.setTextColor(Color.GRAY);
+ } else {
+ mBtnBLabelTxt.setTextColor(Color.WHITE);
+ mBtnBStatusTxt.setTextColor(Color.WHITE);
+ }
+ if (!mButtonAttributes.mHasBtnC) {
+ mBtnCLabelTxt.setTextColor(Color.GRAY);
+ mBtnCStatusTxt.setTextColor(Color.GRAY);
+ } else {
+ mBtnCLabelTxt.setTextColor(Color.WHITE);
+ mBtnCStatusTxt.setTextColor(Color.WHITE);
+ }
+ if (!mButtonAttributes.mHasBtnD) {
+ mBtnDLabelTxt.setTextColor(Color.GRAY);
+ mBtnDStatusTxt.setTextColor(Color.GRAY);
+ } else {
+ mBtnDLabelTxt.setTextColor(Color.WHITE);
+ mBtnDStatusTxt.setTextColor(Color.WHITE);
+ }
+ } else {
+ mBtnALabelTxt.setTextColor(Color.GRAY);
+ mBtnAStatusTxt.setTextColor(Color.GRAY);
+ mBtnBLabelTxt.setTextColor(Color.GRAY);
+ mBtnBStatusTxt.setTextColor(Color.GRAY);
+ mBtnCLabelTxt.setTextColor(Color.GRAY);
+ mBtnCStatusTxt.setTextColor(Color.GRAY);
+ mBtnDLabelTxt.setTextColor(Color.GRAY);
+ mBtnDStatusTxt.setTextColor(Color.GRAY);
+ }
+ }
+
+ mBtnAStatusTxt.setText(getString(
+ mHasBtnA ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+ mBtnBStatusTxt.setText(getString(
+ mHasBtnB ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+ mBtnCStatusTxt.setText(getString(
+ mHasBtnC ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+ mBtnDStatusTxt.setText(getString(
+ mHasBtnD ? R.string.uapButtonsRecognized : R.string.uapButtonsNotRecognized));
+ }
+
+ private void calculateMatch() {
+ if (mIsPeripheralAttached && mSelectedProfile != null) {
+ ProfileButtonAttributes mButtonAttributes = mSelectedProfile.getButtonAttributes();
+ boolean match = mButtonAttributes != null;
+ if (match && mButtonAttributes.mHasBtnA != mHasBtnA) {
+ match = false;
+ }
+ if (match && mButtonAttributes.mHasBtnB != mHasBtnB) {
+ match = false;
+ }
+ if (match && mButtonAttributes.mHasBtnC != mHasBtnC) {
+ match = false;
+ }
+ if (match && mButtonAttributes.mHasBtnD != mHasBtnD) {
+ match = false;
+ }
+ Log.i(TAG, "match:" + match);
+ getPassButton().setEnabled(match);
+ } else {
+ getPassButton().setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ Log.i(TAG, "onKeyDown(" + keyCode + ")");
+ switch (keyCode) {
+ // Function A control event
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ mHasBtnA = true;
+ break;
+
+ // Function B control event
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ mHasBtnB = true;
+ break;
+
+ // Function C control event
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ mHasBtnC = true;
+ break;
+
+ // Function D control event
+ case KeyEvent.KEYCODE_VOICE_ASSIST:
+ mHasBtnD = true;
+ break;
+ }
+
+ showButtonsState();
+ calculateMatch();
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ //
+ // USBAudioPeripheralActivity
+ //
+ public void updateConnectStatus() {
+ mHasBtnA = mHasBtnB = mHasBtnC = mHasBtnD = false;
+ showButtonsState();
+ calculateMatch();
+ }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
new file mode 100644
index 0000000..c2b3b2c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayActivity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralPlayActivity extends USBAudioPeripheralPlayerActivity {
+ private static final String TAG = "USBAudioPeripheralPlayActivity";
+
+ // Widgets
+ private Button mPlayBtn;
+ private LocalClickListener mButtonClickListener = new LocalClickListener();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.uap_play_panel);
+
+ connectPeripheralStatusWidgets();
+
+ // Local widgets
+ mPlayBtn = (Button)findViewById(R.id.uap_playPlayBtn);
+ mPlayBtn.setOnClickListener(mButtonClickListener);
+
+ setupPlayer();
+
+ setPassFailButtonClickListeners();
+ }
+
+ //
+ // USBAudioPeripheralActivity
+ //
+ public void updateConnectStatus() {
+ getPassButton().setEnabled(mOutputDevInfo != null);
+ }
+
+ public class LocalClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.uap_playPlayBtn:
+ Log.i(TAG, "Play Button Pressed");
+ if (!isPlaying()) {
+ startPlay();
+ mPlayBtn.setText(getString(R.string.audio_uap_play_stopBtn));
+ } else {
+ stopPlay();
+ mPlayBtn.setText(getString(R.string.audio_uap_play_playBtn));
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
new file mode 100644
index 0000000..675d658
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.cts.verifier.audio.audiolib.SignalGenerator;
+import com.android.cts.verifier.audio.audiolib.StreamPlayer;
+import com.android.cts.verifier.audio.audiolib.WaveTableFloatFiller;
+import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+
+public abstract class USBAudioPeripheralPlayerActivity extends USBAudioPeripheralActivity {
+ private static final String TAG = "USBAudioPeripheralPlayerActivity";
+
+ protected int mSystemBufferSize;
+
+ // Player
+ protected boolean mIsPlaying = false;
+ protected StreamPlayer mPlayer = null;
+ protected WaveTableFloatFiller mFiller = null;
+
+ protected float[] mWavBuffer = null;
+
+ protected boolean mOverridePlayFlag = true;
+
+ private static final int WAVBUFF_SIZE_IN_SAMPLES = 2048;
+
+ protected void setupPlayer() {
+ mSystemBufferSize =
+ StreamPlayer.calcNumBurstFrames((AudioManager)getSystemService(Context.AUDIO_SERVICE));
+
+ // the +1 is so we can repeat the 0th sample and simplify the interpolation calculation.
+ mWavBuffer = new float[WAVBUFF_SIZE_IN_SAMPLES + 1];
+
+ SignalGenerator.fillFloatSine(mWavBuffer);
+ mFiller = new WaveTableFloatFiller(mWavBuffer);
+
+ mPlayer = new StreamPlayer();
+ }
+
+ protected void startPlay() {
+ if (mOutputDevInfo != null && !mIsPlaying) {
+ int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mOutputDevInfo);
+ mPlayer.open(numChans, mSystemSampleRate, mSystemBufferSize, mFiller);
+ mPlayer.start();
+ mIsPlaying = true;
+ }
+ }
+
+ protected void stopPlay() {
+ if (mIsPlaying) {
+ mPlayer.stop();
+ mPlayer.close();
+ mIsPlaying = false;
+ }
+ }
+
+ public boolean isPlaying() {
+ return mIsPlaying;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
new file mode 100644
index 0000000..41350c9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.cts.verifier.audio.audiolib.StreamRecorder;
+import com.android.cts.verifier.audio.audiolib.StreamRecorderListener;
+import com.android.cts.verifier.audio.audiolib.WaveScopeView;
+
+import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
+import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+
+import com.android.cts.verifier.R; // needed to access resource in CTSVerifier project namespace.
+
+public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
+ private static final String TAG = "USBAudioPeripheralRecordActivity";
+
+ // Recorder
+ private StreamRecorder mRecorder = null;
+ private RecordListener mRecordListener = null;
+ private boolean mIsRecording = false;
+
+ // Widgets
+ private Button mRecordBtn;
+ private Button mRecordLoopbackBtn;
+
+ private LocalClickListener mButtonClickListener = new LocalClickListener();
+
+ private WaveScopeView mWaveView = null;
+
+ private void connectWaveView() {
+ // Log.i(TAG, "connectWaveView() rec:" + (mRecorder != null));
+ if (mRecorder != null) {
+ float[] smplFloatBuff = mRecorder.getBurstBuffer();
+ int numChans = mRecorder.getNumChannels();
+ int numFrames = smplFloatBuff.length / numChans;
+ mWaveView.setPCMFloatBuff(smplFloatBuff, numChans, numFrames);
+ mWaveView.invalidate();
+
+ mRecorder.setListener(mRecordListener);
+ }
+ }
+
+ public boolean startRecording(boolean withLoopback) {
+ if (mInputDevInfo == null) {
+ return false;
+ }
+
+ if (mRecorder == null) {
+ mRecorder = new StreamRecorder();
+ } else if (mRecorder.isRecording()) {
+ mRecorder.stop();
+ }
+
+ int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mInputDevInfo);
+
+ if (mRecorder.open(numChans, mSystemSampleRate, mSystemBufferSize)) {
+ connectWaveView(); // Setup the WaveView
+
+ mIsRecording = mRecorder.start();
+
+ if (withLoopback) {
+ startPlay();
+ }
+
+ return mIsRecording;
+ } else {
+ return false;
+ }
+ }
+
+ public void stopRecording() {
+ if (mRecorder != null) {
+ mRecorder.stop();
+ }
+
+ if (mPlayer != null && mPlayer.isPlaying()) {
+ mPlayer.stop();
+ }
+
+ mIsRecording = false;
+ }
+
+ public boolean isRecording() {
+ return mIsRecording;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.uap_record_panel);
+
+ connectPeripheralStatusWidgets();
+
+ // Local widgets
+ mRecordBtn = (Button)findViewById(R.id.uap_recordRecordBtn);
+ mRecordBtn.setOnClickListener(mButtonClickListener);
+ mRecordLoopbackBtn = (Button)findViewById(R.id.uap_recordRecordLoopBtn);
+ mRecordLoopbackBtn.setOnClickListener(mButtonClickListener);
+
+ setupPlayer();
+
+ mRecorder = new StreamRecorder();
+ mRecordListener = new RecordListener();
+
+ mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
+ mWaveView.setBackgroundColor(Color.DKGRAY);
+ mWaveView.setTraceColor(Color.WHITE);
+
+ setPassFailButtonClickListeners();
+ }
+
+ //
+ // USBAudioPeripheralActivity
+ //
+ public void updateConnectStatus() {
+ getPassButton().setEnabled(mOutputDevInfo != null);
+ }
+
+ public class LocalClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.uap_recordRecordBtn:
+ Log.i(TAG, "Record Button Pressed");
+ if (!isPlaying()) {
+ startRecording(false);
+ mRecordBtn.setText(getString(R.string.audio_uap_record_stopBtn));
+ mRecordLoopbackBtn.setEnabled(false);
+ } else {
+ stopRecording();
+ mRecordBtn.setText(getString(R.string.audio_uap_record_recordBtn));
+ mRecordLoopbackBtn.setEnabled(true);
+ }
+ break;
+
+ case R.id.uap_recordRecordLoopBtn:
+ Log.i(TAG, "Record Loopback Button Pressed");
+ if (!isPlaying()) {
+ startRecording(true);
+ mRecordLoopbackBtn.setText(getString(R.string.audio_uap_record_stopBtn));
+ mRecordBtn.setEnabled(false);
+ } else {
+ stopRecording();
+ mRecordLoopbackBtn.setText(
+ getString(R.string.audio_uap_record_recordLoopbackBtn));
+ mRecordBtn.setEnabled(true);
+ }
+ break;
+ }
+ }
+ }
+
+ private class RecordListener extends StreamRecorderListener {
+ /*package*/ RecordListener() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ // Log.i(TAG, "RecordListener.HandleMessage(" + msg.what + ")");
+ switch (msg.what) {
+ case MSG_START:
+ break;
+
+ case MSG_BUFFER_FILL:
+ mWaveView.invalidate();
+ break;
+
+ case MSG_STOP:
+ break;
+ }
+ }
+ }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
new file mode 100644
index 0000000..fd4d6c9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+/**
+ * An interface for objects which provide streamed audio data to a StreamPlayer instance.
+ */
+public interface AudioFiller {
+ /**
+ * Reset a stream to the beginning.
+ */
+ public void reset();
+
+ /**
+ * Process a request for audio data.
+ * @param buffer The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return The number of frames actually generated. If this value is less than that
+ * requested, it may be interpreted by the player as the end of playback.
+ */
+ public int fill(float[] buffer, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
new file mode 100644
index 0000000..002f460
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+
+// TODO - This functionality probably exists in the framework function. Remove this and
+// use that instead.
+public class AudioUtils {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AudioUtils";
+
+ public static int countIndexChannels(int chanConfig) {
+ return Integer.bitCount(chanConfig & ~0x80000000);
+ }
+
+ public static int countToIndexMask(int chanCount) {
+ return (1 << chanCount) - 1;
+ }
+
+ public static int countToOutPositionMask(int channelCount) {
+ switch (channelCount) {
+ case 1:
+ return AudioFormat.CHANNEL_OUT_MONO;
+
+ case 2:
+ return AudioFormat.CHANNEL_OUT_STEREO;
+
+ case 4:
+ return AudioFormat.CHANNEL_OUT_QUAD;
+
+ default:
+ return AudioTrack.ERROR_BAD_VALUE;
+ }
+ }
+
+ public static int countToInPositionMask(int channelCount) {
+ switch (channelCount) {
+ case 1:
+ return AudioFormat.CHANNEL_IN_MONO;
+
+ case 2:
+ return AudioFormat.CHANNEL_IN_STEREO;
+
+ default:
+ return AudioRecord.ERROR_BAD_VALUE;
+ }
+ }
+
+ // Encodings
+ public static int sampleSizeInBytes(int encoding) {
+ switch (encoding) {
+ case AudioFormat.ENCODING_PCM_16BIT:
+ return 2;
+
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ return 4;
+
+ default:
+ return 0;
+ }
+ }
+
+ public static int calcFrameSizeInBytes(int encoding, int numChannels) {
+ return sampleSizeInBytes(encoding) * numChannels;
+ }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README
new file mode 100644
index 0000000..2309962
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/README
@@ -0,0 +1 @@
+This code is forked from (AndroidStudio Project) <branch>vendor/box/team/audio/AudioLibApp/audiolib
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
new file mode 100644
index 0000000..2d91acf
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+/**
+ * Generates buffers of PCM data.
+ */
+public class SignalGenerator {
+ @SuppressWarnings("unused")
+ private static final String TAG = "SignalGenerator";
+
+ /**
+ * Fills a PCMFloat buffer with 1 cycle of a sine wave.
+ * NOTE: The first and last (index 0 and size-1) are filled with the
+ * sample value because WaveTableFloatFiller assumes this (to make the
+ * interpolation calculation at the end of wavetable more efficient.
+ */
+ static public void fillFloatSine(float[] buffer) {
+ int size = buffer.length;
+ float incr = ((float)Math.PI * 2.0f) / (float)(size - 1);
+ for(int index = 0; index < size; index++) {
+ buffer[index] = (float)Math.sin(index * incr);
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
new file mode 100644
index 0000000..12f1853
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+
+import android.util.Log;
+
+/**
+ * Plays audio data from a stream. Audio data comes from a provided AudioFiller subclass instance.
+ */
+public class StreamPlayer {
+ @SuppressWarnings("unused")
+ private static String TAG = "StreamPlayer";
+
+ private int mSampleRate;
+ private int mNumChans;
+
+ private AudioFiller mFiller;
+
+ private Thread mPlayerThread;
+
+ private AudioTrack mAudioTrack;
+ private int mNumAudioTrackFrames; // number of frames for INTERNAL AudioTrack buffer
+
+ // The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack.
+ private int mNumBurstFrames;
+ private float[] mBurstBuffer;
+
+ private float[] mChanGains;
+ private volatile boolean mPlaying;
+
+ private AudioDeviceInfo mRoutingDevice;
+
+ public StreamPlayer() {}
+
+ public int getSampleRate() { return mSampleRate; }
+ public int getNumBurstFrames() { return mNumBurstFrames; }
+
+ public void setChanGains(float[] chanGains) {
+ mChanGains = chanGains;
+ }
+
+ public boolean isPlaying() { return mPlaying; }
+
+ private void applyChannelGains() {
+ if (mChanGains != null) {
+ int buffIndex = 0;
+ for (int frame = 0; frame < mNumBurstFrames; frame++) {
+ for (int chan = 0; chan < mNumChans; chan++) {
+ mBurstBuffer[buffIndex++] *= mChanGains[chan];
+ }
+ }
+ }
+ }
+
+ public void setFiller(AudioFiller filler) { mFiller = filler; }
+
+ public void setRouting(AudioDeviceInfo routingDevice) {
+ mRoutingDevice = routingDevice;
+ if (mAudioTrack != null) {
+ mAudioTrack.setPreferredDevice(mRoutingDevice);
+ }
+ }
+
+ public AudioTrack getAudioTrack() { return mAudioTrack; }
+
+ private void allocBurstBuffer() {
+ mBurstBuffer = new float[mNumBurstFrames * mNumChans];
+ }
+
+ private static int calcNumBufferBytes(int sampleRate, int numChannels, int encoding) {
+ return AudioTrack.getMinBufferSize(sampleRate,
+ AudioUtils.countToOutPositionMask(numChannels),
+ encoding);
+ }
+
+ private static int calcNumBufferFrames(int sampleRate, int numChannels, int encoding) {
+ return calcNumBufferBytes(sampleRate, numChannels, encoding)
+ / AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
+ }
+
+ public static int calcNumBurstFrames(AudioManager am) {
+ String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ return Integer.parseInt(framesPerBuffer, 10);
+ }
+
+ public boolean open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler) {
+// Log.i(TAG, "StreamPlayer.open(chans:" + numChans + ", rate:" + sampleRate +
+// ", frames:" + numBurstFrames);
+
+ mNumChans = numChans;
+ mSampleRate = sampleRate;
+ mNumBurstFrames = numBurstFrames;
+
+ mNumAudioTrackFrames =
+ calcNumBufferFrames(sampleRate, numChans, AudioFormat.ENCODING_PCM_FLOAT);
+
+ mFiller = filler;
+
+ int bufferSizeInBytes = mNumAudioTrackFrames *
+ AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChans);
+ try {
+ mAudioTrack = new AudioTrack.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+ .setSampleRate(mSampleRate)
+ .setChannelIndexMask(AudioUtils.countToIndexMask(mNumChans))
+ .build())
+ .setBufferSizeInBytes(bufferSizeInBytes)
+ .build();
+
+ allocBurstBuffer();
+ return true;
+ } catch (UnsupportedOperationException ex) {
+ Log.i(TAG, "Couldn't open AudioTrack: " + ex);
+ mAudioTrack = null;
+ return false;
+ }
+ }
+
+ private void waitForPlayerThreadToExit() {
+ try {
+ if (mPlayerThread != null) {
+ mPlayerThread.join();
+ mPlayerThread = null;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void close() {
+ stop();
+
+ waitForPlayerThreadToExit();
+
+ if (mAudioTrack != null) {
+ mAudioTrack.release();
+ mAudioTrack = null;
+ }
+ }
+
+ public boolean start() {
+ if (!mPlaying && mAudioTrack != null) {
+ mPlaying = true;
+
+ waitForPlayerThreadToExit(); // just to be sure.
+
+ mPlayerThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
+ mPlayerThread.start();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void stop() {
+ mPlaying = false;
+ }
+
+ //
+ // StreamPlayerRunnable
+ //
+ private class StreamPlayerRunnable implements Runnable {
+ @Override
+ public void run() {
+ final int numBurstSamples = mNumBurstFrames * mNumChans;
+
+ mAudioTrack.play();
+ while (true) {
+ boolean playing;
+ synchronized(this) {
+ playing = mPlaying;
+ }
+ if (!playing) {
+ break;
+ }
+
+ mFiller.fill(mBurstBuffer, mNumBurstFrames, mNumChans);
+ if (mChanGains != null) {
+ applyChannelGains();
+ }
+ int numSamplesWritten =
+ mAudioTrack.write(mBurstBuffer, 0, numBurstSamples, AudioTrack.WRITE_BLOCKING);
+ if (numSamplesWritten < 0) {
+ // error
+ Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
+ stop();
+ } else if (numSamplesWritten < numBurstSamples) {
+ // end of stream
+ Log.i(TAG, "Stream Complete.");
+ stop();
+ }
+ }
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
new file mode 100644
index 0000000..ed25743
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+
+import android.util.Log;
+
+/**
+ * Records audio data to a stream.
+ */
+public class StreamRecorder {
+ @SuppressWarnings("unused")
+ private static final String TAG = "StreamRecorder";
+
+ // Sample Buffer
+ private float[] mBurstBuffer;
+ private int mNumBurstFrames;
+ private int mNumChannels;
+
+ // Recording attributes
+ private int mSampleRate;
+
+ // Recording state
+ Thread mRecorderThread = null;
+ private AudioRecord mAudioRecord = null;
+ private boolean mRecording = false;
+
+ private StreamRecorderListener mListener = null;
+
+ private AudioDeviceInfo mRoutingDevice = null;
+
+ public StreamRecorder() {}
+
+ public int getNumBurstFrames() { return mNumBurstFrames; }
+ public int getSampleRate() { return mSampleRate; }
+
+ /*
+ * State
+ */
+ public static int calcNumBufferBytes(int numChannels, int sampleRate, int encoding) {
+ // NOTE: Special handling of 4-channels. There is currently no AudioFormat positional
+ // constant for 4-channels of input, so in this case, calculate for 2 and double it.
+ int numBytes = 0;
+ if (numChannels == 4) {
+ numBytes = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO,
+ encoding);
+ numBytes *= 2;
+ } else {
+ numBytes = AudioRecord.getMinBufferSize(sampleRate,
+ AudioUtils.countToInPositionMask(numChannels), encoding);
+ }
+
+ return numBytes;
+ }
+
+ public static int calcNumBufferFrames(int numChannels, int sampleRate, int encoding) {
+ return calcNumBufferBytes(numChannels, sampleRate, encoding) /
+ AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
+ }
+
+ public boolean isInitialized() {
+ return mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED;
+ }
+
+ public boolean isRecording() { return mRecording; }
+
+ public void setRouting(AudioDeviceInfo routingDevice) {
+ Log.i(TAG, "setRouting(" + (routingDevice != null ? routingDevice.getId() : -1) + ")");
+ mRoutingDevice = routingDevice;
+ if (mAudioRecord != null) {
+ mAudioRecord.setPreferredDevice(mRoutingDevice);
+ }
+ }
+
+ /*
+ * Accessors
+ */
+ public float[] getBurstBuffer() { return mBurstBuffer; }
+
+ public int getNumChannels() { return mNumChannels; }
+
+ /*
+ * Events
+ */
+ public void setListener(StreamRecorderListener listener) {
+ mListener = listener;
+ }
+
+ private void waitForRecorderThreadToExit() {
+ try {
+ if (mRecorderThread != null) {
+ mRecorderThread.join();
+ mRecorderThread = null;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private boolean open_internal(int numChans, int sampleRate) {
+ Log.i(TAG, "StreamRecorder.open_internal(chans:" + numChans + ", rate:" + sampleRate);
+
+ mNumChannels = numChans;
+ mSampleRate = sampleRate;
+
+ int chanMask = AudioUtils.countToIndexMask(numChans);
+ int bufferSizeInBytes =
+ AudioRecord.getMinBufferSize(mSampleRate, chanMask, AudioFormat.ENCODING_PCM_FLOAT);
+ try {
+ mAudioRecord = new AudioRecord.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+ .setSampleRate(mSampleRate)
+ .setChannelIndexMask(chanMask)
+ .build())
+ .setBufferSizeInBytes(bufferSizeInBytes)
+ .build();
+
+ return true;
+ } catch (UnsupportedOperationException ex) {
+ Log.i(TAG, "Couldn't open AudioRecord: " + ex);
+ mAudioRecord = null;
+ return false;
+ }
+ }
+
+ public boolean open(int numChans, int sampleRate, int numBurstFrames) {
+ boolean sucess = open_internal(numChans, sampleRate);
+ if (sucess) {
+ mNumBurstFrames = numBurstFrames;
+ mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
+ }
+
+ return sucess;
+ }
+
+ public void close() {
+ stop();
+
+ waitForRecorderThreadToExit();
+
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+
+ public boolean start() {
+ mAudioRecord.setPreferredDevice(mRoutingDevice);
+
+ if (mListener != null) {
+ mListener.sendEmptyMessage(StreamRecorderListener.MSG_START);
+ }
+
+ try {
+ mAudioRecord.startRecording();
+ } catch (IllegalStateException ex) {
+ Log.i("", "ex: " + ex);
+ }
+ mRecording = true;
+
+ waitForRecorderThreadToExit(); // just to be sure.
+
+ mRecorderThread = new Thread(new StreamRecorderRunnable(), "StreamRecorder Thread");
+ mRecorderThread.start();
+
+ return true;
+ }
+
+ public void stop() {
+ if (mRecording) {
+ mRecording = false;
+ }
+ }
+
+ /*
+ * StreamRecorderRunnable
+ */
+ private class StreamRecorderRunnable implements Runnable {
+ @Override
+ public void run() {
+ final int numBurstSamples = mNumBurstFrames * mNumChannels;
+ while (mRecording) {
+ int numReadSamples = mAudioRecord.read(
+ mBurstBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
+
+ if (numReadSamples < 0) {
+ // error
+ Log.i(TAG, "AudioRecord write error: " + numReadSamples);
+ stop();
+ } else if (numReadSamples < numBurstSamples) {
+ // got less than requested?
+ Log.i(TAG, "AudioRecord Underflow: " + numReadSamples +
+ " vs. " + numBurstSamples);
+ stop();
+ }
+
+ if (mListener != null && numReadSamples == numBurstSamples) {
+ mListener.sendEmptyMessage(StreamRecorderListener.MSG_BUFFER_FILL);
+ }
+ }
+
+ if (mListener != null) {
+ // TODO: on error or underrun we may be send bogus data.
+ mListener.sendEmptyMessage(StreamRecorderListener.MSG_STOP);
+ }
+ mAudioRecord.stop();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
new file mode 100644
index 0000000..c542432
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public class StreamRecorderListener extends Handler {
+ @SuppressWarnings("unused")
+ private static final String TAG = "StreamRecorderListener";
+
+ public static final int MSG_START = 0;
+ public static final int MSG_BUFFER_FILL = 1;
+ public static final int MSG_STOP = 2;
+
+ public StreamRecorderListener(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START:
+ case MSG_BUFFER_FILL:
+ case MSG_STOP:
+ break;
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java
new file mode 100644
index 0000000..c52d284
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveScopeView.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class WaveScopeView extends View {
+ @SuppressWarnings("unused")
+ private static final String TAG = "WaveScopeView";
+
+ private final Paint mPaint = new Paint();
+
+ private int mBackgroundColor = Color.WHITE;
+ private int mTraceColor = Color.BLACK;
+
+ private short[] mPCM16Buffer;
+ private float[] mPCMFloatBuffer;
+
+ private int mNumChannels = 2;
+ private int mNumFrames = 0;
+
+ private float[] mPointsBuffer;
+
+ // Horrible kludge
+ private static int mCachedWidth = 0;
+
+ public WaveScopeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setBackgroundColor(int color) { mBackgroundColor = color; }
+
+ public void setTraceColor(int color) { mTraceColor = color; }
+
+ public void setPCM16Buff(short[] smpl16Buff, int numChans, int numFrames) {
+ mPCM16Buffer = smpl16Buff;
+ mPCMFloatBuffer = null;
+
+ mNumChannels = numChans;
+ mNumFrames = numFrames;
+
+ setupPointBuffer();
+
+ invalidate();
+ }
+
+ public void setPCMFloatBuff(float[] smplFloatBuff, int numChans, int numFrames) {
+ mPCMFloatBuffer = smplFloatBuff;
+ mPCM16Buffer = null;
+
+ mNumChannels = numChans;
+ mNumFrames = numFrames;
+
+ setupPointBuffer();
+
+ invalidate();
+ }
+
+ private void setupPointBuffer() {
+ int width = getWidth();
+
+ // Horrible kludge
+ if (width == 0) {
+ width = mCachedWidth;
+ } else {
+ mCachedWidth = width;
+ }
+
+ // Canvas.drawLines() uses 2 points (float pairs) per line-segment
+ mPointsBuffer = new float[mNumFrames * 4];
+
+ float xIncr = (float) width / (float) mNumFrames;
+
+ float X = 0;
+ int len = mPointsBuffer.length;
+ for (int pntIndex = 0; pntIndex < len;) {
+ mPointsBuffer[pntIndex] = X;
+ pntIndex += 2; // skip Y
+
+ X += xIncr;
+
+ mPointsBuffer[pntIndex] = X;
+ pntIndex += 2; // skip Y
+ }
+ }
+
+ /**
+ * Draws 1 channel of an interleaved block of SMPL16 samples.
+ * @param cvs The Canvas to draw into.
+ * @param samples The (potentially) multi-channel sample block.
+ * @param numFrames The number of FRAMES in the specified sample block.
+ * @param numChans The number of interleaved channels in the specified sample block.
+ * @param chanIndex The (0-based) index of the channel to draw.
+ * @param zeroY The Y-coordinate of sample value 0 (zero).
+ */
+ private void drawChannel16(Canvas cvs, short[] samples, int numFrames, int numChans,
+ int chanIndex, float zeroY) {
+ float yScale = getHeight() / (float) (Short.MAX_VALUE * 2 * numChans);
+ int pntIndex = 1; // of the first Y coordinate
+ float Y = zeroY;
+ int smpl = chanIndex;
+ for (int frame = 0; frame < numFrames; frame++) {
+ mPointsBuffer[pntIndex] = Y;
+ pntIndex += 2;
+
+ Y = zeroY - (samples[smpl] * yScale);
+
+ mPointsBuffer[pntIndex] = Y;
+ pntIndex += 2;
+
+ smpl += numChans;
+ }
+ cvs.drawLines(mPointsBuffer, mPaint);
+ }
+
+ /**
+ * Draws 1 channel of an interleaved block of FLOAT samples.
+ * @param cvs The Canvas to draw into.
+ * @param samples The (potentially) multi-channel sample block.
+ * @param numFrames The number of FRAMES in the specified sample block.
+ * @param numChans The number of interleaved channels in the specified sample block.
+ * @param chanIndex The (0-based) index of the channel to draw.
+ * @param zeroY The Y-coordinate of sample value 0 (zero).
+ */
+ private void drawChannelFloat(Canvas cvs, float[] samples, int numFrames, int numChans,
+ int chanIndex, float zeroY) {
+ float yScale = getHeight() / (float) (2 * numChans);
+ int pntIndex = 1; // of the first Y coordinate
+ float Y = zeroY;
+ int smpl = chanIndex;
+ for (int frame = 0; frame < numFrames; frame++) {
+ mPointsBuffer[pntIndex] = Y;
+ pntIndex += 2;
+
+ Y = zeroY - (samples[smpl] * yScale);
+
+ mPointsBuffer[pntIndex] = Y;
+ pntIndex += 2;
+
+ smpl += numChans;
+ }
+ cvs.drawLines(mPointsBuffer, mPaint);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int height = getHeight();
+ mPaint.setColor(mBackgroundColor);
+ canvas.drawRect(0, 0, getWidth(), height, mPaint);
+
+ mPaint.setColor(mTraceColor);
+ if (mPCM16Buffer != null) {
+ float yOffset = height / (2.0f * mNumChannels);
+ float yDelta = height / (float) mNumChannels;
+ for(int channel = 0; channel < mNumChannels; channel++) {
+ drawChannel16(canvas, mPCM16Buffer, mNumFrames, mNumChannels, channel, yOffset);
+ yOffset += yDelta;
+ }
+ } else if (mPCMFloatBuffer != null) {
+ float yOffset = height / (2.0f * mNumChannels);
+ float yDelta = height / (float) mNumChannels;
+ for(int channel = 0; channel < mNumChannels; channel++) {
+ drawChannelFloat(canvas, mPCMFloatBuffer, mNumFrames, mNumChannels, channel, yOffset);
+ yOffset += yDelta;
+ }
+ }
+ // Log.i("WaveView", "onDraw() - done");
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
new file mode 100644
index 0000000..1040eab
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.audiolib;
+
+/**
+ * A AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
+ */
+public class WaveTableFloatFiller implements AudioFiller {
+ @SuppressWarnings("unused")
+ private static String TAG = "WaveTableFloatFiller";
+
+ private float[] mWaveTbl = null;
+ private int mNumWaveTblSamples = 0;
+ private float mSrcPhase = 0.0f;
+
+ private float mSampleRate = 48000;
+ private float mFreq = 1000; // some arbitrary frequency
+ private float mFN = 1.0f; // The "nominal" frequency, essentially how much much of the
+ // wave table needs to be played to get one cycle at the
+ // sample rate. Used to calculate the phase increment
+
+ public WaveTableFloatFiller(float[] waveTbl) {
+ setWaveTable(waveTbl);
+ }
+
+ private void calcFN() {
+ mFN = mSampleRate / (float)mNumWaveTblSamples;
+ }
+
+ public void setWaveTable(float[] waveTbl) {
+ mWaveTbl = waveTbl;
+ mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
+
+ calcFN();
+ }
+
+ public void setSampleRate(float sampleRate) {
+ mSampleRate = sampleRate;
+ calcFN();
+ }
+
+ public void setFreq(float freq) {
+ mFreq = freq;
+ }
+
+ @Override
+ public void reset() {
+ mSrcPhase = 0.0f;
+ }
+
+ public int fill(float[] buffer, int numFrames, int numChans) {
+ final float phaseIncr = mFreq / mFN;
+ int outIndex = 0;
+ for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+ // 'mod' back into the waveTable
+ while (mSrcPhase >= (float)mNumWaveTblSamples) {
+ mSrcPhase -= (float)mNumWaveTblSamples;
+ }
+
+ // linear-interpolate
+ int srcIndex = (int)mSrcPhase;
+ float delta0 = mSrcPhase - (float)srcIndex;
+ float delta1 = 1.0f - delta0;
+ float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
+
+ for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+ buffer[outIndex++] = value;
+ }
+
+ mSrcPhase += phaseIncr;
+ }
+
+ return numFrames;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java
new file mode 100644
index 0000000..e9acacc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/AudioStringsHelper.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+import android.support.annotation.NonNull;
+
+public class AudioStringsHelper {
+ // These correspond to encoding constants defined in AudioFormats.java
+ static private final String formatStrings[] = {
+ "0 Unknown Format",
+ "1 Unknown Format",
+ "2 ENCODING_PCM_16BIT",
+ "3 ENCODING_PCM_8BIT",
+ "4 ENCODING_PCM_FLOAT",
+ "5 ENCODING_AC3",
+ "6 ENCODING_E_AC3" };
+
+ // These too.
+ static private final String shortFormatStrings[] = {
+ "??? [0]",
+ "??? [1]",
+ "PCM16",
+ "PCM8",
+ "PCMFLOAT",
+ "AC3",
+ "E_AC3" };
+
+ static private String encodingToString(int fmt) {
+ return fmt < formatStrings.length ? formatStrings[fmt] : ("" + fmt + " Unknown Format ");
+ }
+
+ static private String encodingToShortString(int fmt) {
+ return fmt < shortFormatStrings.length
+ ? shortFormatStrings[fmt]
+ : ("" + fmt + " Unknown Format ");
+ }
+
+ static private String getRateString(int rate) {
+ if (rate % 1000 == 0) {
+ return "" + rate/1000 + "K";
+ } else {
+ return "" + (float)rate/1000 + "K";
+ }
+ }
+
+ @NonNull
+ static public String buildRatesStr(int[] rates) {
+ StringBuilder builder = new StringBuilder();
+ for(int rateIndex = 0; rateIndex < rates.length; rateIndex++) {
+ builder.append(getRateString(rates[rateIndex]));
+ if (rateIndex < rates.length - 1) {
+ builder.append(", ");
+ }
+ }
+ return builder.toString();
+ }
+
+ @NonNull
+ static public String buildEncodingsStr(int encodings[]) {
+ StringBuilder builder = new StringBuilder();
+ for(int encodingIndex = 0; encodingIndex < encodings.length; encodingIndex++) {
+ builder.append(encodingToShortString(encodings[encodingIndex]));
+ if (encodingIndex < encodings.length - 1) {
+ builder.append(", ");
+ }
+ }
+ return builder.toString();
+ }
+
+ @NonNull
+ static public String buildChannelCountsStr(int counts[]) {
+ return makeIntList(counts);
+ }
+
+ @NonNull
+ public static String makeRatesList(int[] values) {
+ return makeIntList(values);
+ }
+
+ @NonNull
+ public static String makeIntList(int[] values) {
+ StringBuilder sb = new StringBuilder();
+ for (int index = 0; index < values.length; index++) {
+ sb.append(values[index]);
+ if (index < values.length - 1) {
+ sb.append(",");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @NonNull
+ public static String makeHexList(int[] values) {
+ StringBuilder sb = new StringBuilder();
+ for (int index = 0; index < values.length; index++) {
+ sb.append("0x" + Integer.toHexString(values[index]).toUpperCase());
+ if (index < values.length - 1) {
+ sb.append(",");
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
new file mode 100644
index 0000000..4d14347
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ListsHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+public class ListsHelper {
+ static public boolean isMatch(int[] a, int[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+
+ int len = a.length;
+ for (int index = 0; index < len; index++) {
+ if (a[index] != b[index]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
new file mode 100644
index 0000000..4c3fbb8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/PeripheralProfile.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+import android.media.AudioDeviceInfo;
+import android.support.annotation.NonNull;
+
+import com.android.cts.verifier.audio.peripheralprofile.ListsHelper;
+
+import java.io.IOException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class PeripheralProfile extends DefaultHandler {
+ private String mProfileName;
+ private String mProfileDescription;
+
+ private String mProductName = ""; // From AudioDeviceInfo
+
+ public class ProfileAttributes {
+ public int[] mChannelCounts;
+ public int[] mChannelIndexMasks;
+ public int[] mChannelPositionMasks;
+ public int[] mEncodings;
+ public int[] mSampleRates;
+ }
+
+ ProfileAttributes mOutputAttributes;
+ ProfileAttributes mInputAttributes;
+
+ ProfileButtonAttributes mButtonAttributes;
+
+ //
+ // Accessors
+ //
+ public String getName() { return mProfileName; }
+ public String getDescription() { return mProfileDescription; }
+ public String getProductName() { return mProductName; }
+
+ public ProfileAttributes getOutputAttributes() {
+ return mOutputAttributes;
+ }
+ public ProfileAttributes getInputAttributes() {
+ return mInputAttributes;
+ }
+ public ProfileButtonAttributes getButtonAttributes() {
+ return mButtonAttributes;
+ }
+
+ @Override
+ public String toString() { return mProfileName; }
+
+ public PeripheralProfile(String profileName, String profileDescription,
+ AudioDeviceInfo outDeviceInfo,
+ AudioDeviceInfo inDeviceInfo,
+ ProfileButtonAttributes buttonAttributes) {
+ mProfileName = profileName;
+ mProfileDescription = profileDescription;
+
+ if (outDeviceInfo != null) {
+ mProductName = outDeviceInfo.getProductName().toString();
+
+ mOutputAttributes = new ProfileAttributes();
+ mOutputAttributes.mChannelCounts =
+ outDeviceInfo.getChannelCounts();
+ mOutputAttributes.mChannelIndexMasks =
+ outDeviceInfo.getChannelIndexMasks();
+ mOutputAttributes.mChannelPositionMasks =
+ outDeviceInfo.getChannelMasks();
+ mOutputAttributes.mEncodings = outDeviceInfo.getEncodings();
+ mOutputAttributes.mSampleRates = outDeviceInfo.getSampleRates();
+ } else {
+ mOutputAttributes = null;
+ }
+
+ if (inDeviceInfo != null) {
+ mProductName = outDeviceInfo.getProductName().toString();
+
+ mInputAttributes = new ProfileAttributes();
+ mInputAttributes.mChannelCounts = inDeviceInfo.getChannelCounts();
+ mInputAttributes.mChannelIndexMasks = inDeviceInfo.getChannelIndexMasks();
+ mInputAttributes.mChannelPositionMasks = inDeviceInfo.getChannelMasks();
+ mInputAttributes.mEncodings = inDeviceInfo.getEncodings();
+ mInputAttributes.mSampleRates = inDeviceInfo.getSampleRates();
+ } else {
+ mInputAttributes = null;
+ }
+
+ mButtonAttributes = buttonAttributes;
+ }
+
+ public static boolean matches(ProfileAttributes attribs, AudioDeviceInfo deviceInfo) {
+ boolean match =
+ ListsHelper.isMatch(deviceInfo.getChannelCounts(), attribs.mChannelCounts) &&
+ ListsHelper.isMatch(deviceInfo.getChannelIndexMasks(), attribs.mChannelIndexMasks) &&
+ ListsHelper.isMatch(deviceInfo.getChannelMasks(), attribs.mChannelPositionMasks) &&
+ ListsHelper.isMatch(deviceInfo.getEncodings(), attribs.mEncodings) &&
+ ListsHelper.isMatch(deviceInfo.getSampleRates(), attribs.mSampleRates);
+ return match;
+ }
+
+ //
+ // Peripheral (XML) Loading
+ //
+ private static int[] parseIntList(String intList) {
+ String[] strings = intList.split(",");
+ int[] ints = new int[strings.length];
+ for (int index = 0; index < strings.length; index++) {
+ ints[index] = Integer.parseInt(strings[index]);
+ }
+ return ints;
+ }
+
+ // XML Tags
+ public static final String kTag_Profile = "PeripheralProfile";
+ public static final String kTag_OutputDevInfo = "OutputDevInfo";
+ public static final String kTag_InputDevInfo = "InputDevInfo";
+ public static final String kTag_ButtonInfo = "ButtonInfo";
+
+ // XML Attributes
+ // - Attributes for Profile Tag
+ private static final String kAttr_ProfileName = "ProfileName";
+ private static final String kAttr_ProfileDescription = "ProfileDescription";
+ private static final String kAttr_Product = "ProductName";
+
+ // - Attributes for DevInfo tags
+ private static final String kAttr_ChanCounts = "ChanCounts";
+ private static final String kAttr_ChanPosMasks = "ChanPosMasks";
+ private static final String kAttr_ChanIndexMasks = "ChanIndexMasks";
+ private static final String kAttr_Encodings = "Encodings";
+ private static final String kAttr_SampleRates = "SampleRates";
+ private static final String kAttr_HasBtnA = "HasBtnA";
+ private static final String kAttr_HasBtnB = "HasBtnB";
+ private static final String kAttr_HasBtnC = "HasBtnC";
+ private static final String kAttr_HasBtnD = "HasBtnD";
+
+ private void parseProfileAttributes(ProfileAttributes attribs, String elementName,
+ Attributes xmlAtts) {
+ attribs.mChannelCounts = parseIntList(xmlAtts.getValue(kAttr_ChanCounts));
+ attribs.mChannelPositionMasks = parseIntList(xmlAtts.getValue(kAttr_ChanPosMasks));
+ attribs.mChannelIndexMasks = parseIntList(xmlAtts.getValue(kAttr_ChanIndexMasks));
+ attribs.mEncodings = parseIntList(xmlAtts.getValue(kAttr_Encodings));
+ attribs.mSampleRates = parseIntList(xmlAtts.getValue(kAttr_SampleRates));
+ }
+
+ private void parseProfileButtons(ProfileButtonAttributes buttonAttributes, String elementName,
+ Attributes xmlAtts) {
+ buttonAttributes.mHasBtnA = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnA)) == 1;
+ buttonAttributes.mHasBtnB = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnB)) == 1;
+ buttonAttributes.mHasBtnC = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnC)) == 1;
+ buttonAttributes.mHasBtnD = Integer.parseInt(xmlAtts.getValue(kAttr_HasBtnD)) == 1;
+ }
+
+ //
+ // org.xml.sax.helpers.DefaultHandler overrides
+ //
+ @Override
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (qName.equals(kTag_Profile)) {
+ mProfileName = atts.getValue(kAttr_ProfileName);
+ mProfileDescription = atts.getValue(kAttr_ProfileDescription);
+ mProductName = atts.getValue(kAttr_Product);
+ } else if (qName.equals(kTag_OutputDevInfo)) {
+ mOutputAttributes = new ProfileAttributes();
+ parseProfileAttributes(mOutputAttributes, localName, atts);
+ } else if (qName.equals(kTag_InputDevInfo)) {
+ mInputAttributes = new ProfileAttributes();
+ parseProfileAttributes(mInputAttributes, localName, atts);
+ } else if (qName.equals(kTag_ButtonInfo)) {
+ mButtonAttributes = new ProfileButtonAttributes();
+ parseProfileButtons(mButtonAttributes, localName, atts);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
new file mode 100644
index 0000000..ffe5e6d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileButtonAttributes.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+public class ProfileButtonAttributes {
+ public boolean mHasBtnA;
+ public boolean mHasBtnB;
+ public boolean mHasBtnC;
+ public boolean mHasBtnD;
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
new file mode 100644
index 0000000..1d585ac
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+import android.os.Environment;
+import android.support.annotation.Nullable;
+import android.util.Xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+public class ProfileManager {
+ private static final String mBuiltInprofiles =
+ "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
+ "<ProfileList Version=\"1.0.0\">" +
+ "<PeripheralProfile ProfileName=\"Headset\" ProfileDescription=\"Microsoft LX-3000\" ProductName=\"USB-Audio - Microsoft LifeChat LX-3000\">" +
+ "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
+ "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
+ "<ButtonInfo HasBtnA=\"0\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"0\" />" +
+ "</PeripheralProfile>" +
+ "<PeripheralProfile ProfileName=\"Audio Interface\" ProfileDescription=\"Presonus AudioVox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
+ "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+ "<InputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+ "</PeripheralProfile>" +
+ "<PeripheralProfile ProfileName=\"AudioBox 22VSL\" ProfileDescription=\"Presonus AudioBox 22VSL\" ProductName=\"USB-Audio - AudioBox 22 VSL\">" +
+ "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+ "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+ "</PeripheralProfile>" +
+ "</ProfileList>";
+
+ // XML Tags and Attributes
+ private final static String kTag_ProfileList = "ProfileList";
+ private final static String kAttrName_Version = "Version";
+ private final static String kValueStr_Version = "1.0.0";
+
+ private final ArrayList<PeripheralProfile> mProfiles =
+ new ArrayList<PeripheralProfile>();
+
+ private PeripheralProfile mParsingProfile = null;
+
+ public boolean addProfile(PeripheralProfile profile) {
+ mProfiles.add(profile);
+
+ return true;
+ }
+
+ private class ProfileLoader extends DefaultHandler {
+ @Override
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (localName.equals(kTag_ProfileList)) {
+ // Maybe check the version here.
+ } else if (localName.equals(PeripheralProfile.kTag_Profile)){
+ mParsingProfile = new PeripheralProfile(null, null, null, null, null);
+ mParsingProfile.startElement(namespaceURI, localName, qName, atts);
+ } else {
+ mParsingProfile.startElement(namespaceURI, localName, qName, atts);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if (localName.equals(kTag_ProfileList)) {
+ // Version Checking here maybe?
+ } else if (localName.equals(PeripheralProfile.kTag_Profile)){
+ mProfiles.add(mParsingProfile);
+ mParsingProfile = null;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
+ public boolean loadProfiles(InputStream inStream) {
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ SAXParser sp;
+ try {
+ sp = spf.newSAXParser();
+ XMLReader xr = sp.getXMLReader();
+ xr.setContentHandler(new ProfileLoader());
+ xr.parse(new InputSource(inStream));
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (SAXException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return true;
+ }
+
+ public boolean loadProfiles(String profilesXML) {
+ mProfiles.clear();
+
+ return loadProfiles(new ByteArrayInputStream(profilesXML.getBytes()));
+ }
+
+ public boolean loadProfiles() {
+ return loadProfiles(mBuiltInprofiles);
+ }
+
+ //
+ // Access
+ //
+ public ArrayList<PeripheralProfile> getProfiles() { return mProfiles; }
+
+ public int getNumProfiles() {
+ return mProfiles.size();
+ }
+ public PeripheralProfile getProfile(int index) {
+ return mProfiles.get(index);
+ }
+
+ @Nullable
+ public PeripheralProfile getProfile(String productName) {
+ for(PeripheralProfile profile : mProfiles) {
+ if (productName.equals(profile.getProductName())) {
+ return profile;
+ }
+ }
+ return null;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java
new file mode 100644
index 0000000..54cbf53
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/USBDeviceInfoHelper.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.cts.verifier.audio.peripheralprofile;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.util.Log;
+
+public class USBDeviceInfoHelper {
+ @SuppressWarnings("unused")
+ private static final String TAG = "USBDeviceInfoHelper";
+
+ private static final String kUSBPrefix = "USB-Audio - ";
+
+ // TODO - we can't treat the maximum channel count the same for inputs and outputs
+ public static int calcMaxChannelCount(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo == null) {
+ return 2; // for testing....
+ }
+
+ int maxChanCount = 0;
+ int[] counts = deviceInfo.getChannelCounts();
+ for (int chanCount : counts) {
+ if (chanCount > maxChanCount) {
+ maxChanCount = chanCount;
+ }
+ }
+ return maxChanCount;
+ }
+
+ // TODO This should be in a library module devoted to channel management, not USB.
+ public static int getPlayChanMask(AudioDeviceInfo deviceInfo) {
+ int numChans = calcMaxChannelCount(deviceInfo);
+ switch (numChans) {
+ case 1:
+ return AudioFormat.CHANNEL_OUT_MONO;
+
+ case 2:
+ return AudioFormat.CHANNEL_OUT_STEREO;
+
+ case 4:
+ return AudioFormat.CHANNEL_OUT_QUAD;
+
+ default:
+ // Huh!
+ Log.e(TAG, "getPlayChanMask() Unsupported number of channels: " + numChans);
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+ }
+
+ // TODO This should be in a library module devoted to channel management, not USB.
+ public static int getIndexedChanMask(int numChannels) {
+ return 0x80000000 | numChannels;
+ }
+
+ // TODO This should be in a library module devoted to channel management, not USB.
+ public static int getRecordChanMask(AudioDeviceInfo deviceInfo) {
+ int numChans = calcMaxChannelCount(deviceInfo);
+ switch (numChans) {
+ case 1:
+ return AudioFormat.CHANNEL_IN_MONO;
+
+ case 2:
+ return AudioFormat.CHANNEL_IN_STEREO;
+
+ default:
+ // Huh!
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+ }
+
+ public static String extractDeviceName(String productName) {
+ return productName.startsWith(kUSBPrefix)
+ ? productName.substring(kUSBPrefix.length())
+ : productName;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
old mode 100644
new mode 100755
index b2c6c60..ce68dbd
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
@@ -31,7 +31,6 @@
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.Bundle;
-import android.os.CountDownTimer;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
@@ -47,10 +46,6 @@
private Map<Integer, Integer> mCount;
private int[] mPowerLevel;
- private TextView mTimerText;
- private CountDownTimer mTimer;
- private static final long REFRESH_MAC_TIME = 930000; // 15.5 min
-
private static final int[] POWER_DBM = {-21, -15, -7, 1, 9};
@Override
@@ -60,23 +55,6 @@
setPassFailButtonClickListeners();
setInfoResources(R.string.ble_power_level_name,
R.string.ble_power_level_info, -1);
- getPassButton().setEnabled(false);
-
- mTimerText = (TextView)findViewById(R.id.ble_timer);
- mTimer = new CountDownTimer(REFRESH_MAC_TIME, 1000) {
- @Override
- public void onTick(long millis) {
- int min = (int)millis / 60000;
- int sec = ((int)millis / 1000) % 60;
- mTimerText.setText(min + ":" + sec);
- }
-
- @Override
- public void onFinish() {
- mTimerText.setTextColor(Color.RED);
- mTimerText.setText("Time is up!");
- }
- };
mRssiText = new HashMap<Integer, TextView>();
mCountText = new HashMap<Integer, TextView>();
@@ -169,7 +147,6 @@
for (int i : mPowerLevel) {
mCount.put(i, 0);
}
- mTimer.start();
}
Integer t = mCount.get(powerLevelBit) + 1;
mCount.put(powerLevelBit, t);
@@ -189,10 +166,6 @@
case BleScannerService.BLE_PRIVACY_NEW_MAC_RECEIVE:
Toast.makeText(context, "New MAC address detected", Toast.LENGTH_SHORT)
.show();
- mTimerText.setTextColor(Color.GREEN);
- mTimerText.append(" Get new MAC address.");
- mTimer.cancel();
- getPassButton().setEnabled(true);
break;
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index b4f9724..7064f9f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -17,6 +17,7 @@
package com.android.cts.verifier.managedprovisioning;
import android.app.Activity;
+import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
import android.app.Dialog;
import android.app.KeyguardManager;
@@ -156,6 +157,7 @@
private static final String ORIGINAL_SETTINGS_NAME = "original settings";
private static final int NOTIFICATION_ID = 7;
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
private NotificationManager mNotificationManager;
private Bundle mOriginalSettings;
@@ -170,7 +172,7 @@
private ArrayList<File> mTempFiles = new ArrayList<File>();
private void showNotification(int visibility) {
- final Notification notification = new Notification.Builder(this)
+ final Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.icon)
.setContentTitle(getString(R.string.provisioning_byod_notification_title))
.setVisibility(visibility)
@@ -197,6 +199,9 @@
Intent intent = getIntent();
String action = intent.getAction();
Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
+ mNotificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
// we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
if (action.equals(ACTION_PROFILE_PROVISIONED)) {
@@ -428,6 +433,7 @@
@Override
protected void onDestroy() {
+ mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
cleanUpTempUris();
super.onDestroy();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 28c6cfa..e2d90f7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -404,9 +404,13 @@
}
private void uninstallHelperPackage() {
- getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
- PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
- .getIntentSender());
+ try {
+ getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
+ PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
+ .getIntentSender());
+ } catch (IllegalArgumentException e) {
+ // The package is not installed: that's fine
+ }
}
private void clearAllPolicies() throws Exception {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 4e945e5..3949ed9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -293,6 +293,7 @@
PolicyTransparencyTestListActivity.class);
policyTransparencyTestIntent.putExtra(
PolicyTransparencyTestListActivity.EXTRA_IS_DEVICE_OWNER, true);
+ // So that PolicyTransparencyTestListActivity knows which test to update with the result:
policyTransparencyTestIntent.putExtra(
PolicyTransparencyTestActivity.EXTRA_TEST_ID, POLICY_TRANSPARENCY_TEST_ID);
adapter.add(createTestItem(this, POLICY_TRANSPARENCY_TEST_ID,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
index 5eaf862..0f57b87 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
@@ -45,9 +45,13 @@
AdapterView.OnItemSelectedListener {
public static final String ACTION_SHOW_POLICY_TRANSPARENCY_TEST =
"com.android.cts.verifier.managedprovisioning.action.SHOW_POLICY_TRANSPARENCY_TEST";
+
+ // Identifies a test to perform. Type String. The possible values are the ones underneath.
public static final String EXTRA_TEST =
"com.android.cts.verifier.managedprovisioning.extra.TEST";
+ // In this case: should also contain an extra
+ // {@link CommandReceiverActivity.EXTRA_USER_RESTRICTION}
public static final String TEST_CHECK_USER_RESTRICTION = "check-user-restriction";
public static final String TEST_CHECK_AUTO_TIME_REQUIRED = "check-auto-time-required";
public static final String TEST_CHECK_KEYGURAD_UNREDACTED_NOTIFICATION =
@@ -63,6 +67,8 @@
"com.android.cts.verifier.managedprovisioning.extra.SETTINGS_INTENT_ACTION";
public static final String EXTRA_TITLE =
"com.android.cts.verifier.managedprovisioning.extra.TITLE";
+ // Identifies the test in the calling activity. We will set the result for this test.
+ // Type: String
public static final String EXTRA_TEST_ID =
"com.android.cts.verifier.managedprovisioning.extra.TEST_ID";
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
index c5b4a93..fb7cc12 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
@@ -34,6 +34,8 @@
/**
* Test class to verify transparency for policies enforced by device/profile owner.
+ * Expects to be passed an extra: {@link PolicyTransparencyTestActivity#EXTRA_TEST_ID} to identify
+ * which test in the calling activity should be updated.
*/
public class PolicyTransparencyTestListActivity extends PassFailButtons.TestListActivity
implements View.OnClickListener {
@@ -42,8 +44,13 @@
public static final String EXTRA_IS_DEVICE_OWNER =
"com.android.cts.verifier.managedprovisioning.extra.IS_DEVICE_OWNER";
+ // Pairs of:
+ // - An intent to start PolicyTransparencyTestActivity
+ // - a label to show the user.
+ // These contain all the policies except for the user restriction ones
private static final Pair<Intent, Integer>[] POLICIES;
static {
+ // names of the tests
final String[] policyTests = new String[] {
PolicyTransparencyTestActivity.TEST_CHECK_AUTO_TIME_REQUIRED,
PolicyTransparencyTestActivity.TEST_CHECK_KEYGURAD_UNREDACTED_NOTIFICATION,
@@ -87,6 +94,7 @@
}
}
+ // List of names of test that are also valid for PO
private static final ArrayList<String> ALSO_VALID_FOR_PO = new ArrayList<String>();
static {
ALSO_VALID_FOR_PO.add(
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 935a1b8..968aa5b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -207,6 +207,10 @@
return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
case UserManager.DISALLOW_SHARE_LOCATION:
return pm.hasSystemFeature(PackageManager.FEATURE_LOCATION);
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+ return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ case UserManager.DISALLOW_CONFIG_CREDENTIALS:
+ return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
default:
return true;
}
@@ -222,4 +226,4 @@
this.intentAction = intentAction;
}
}
-}
\ No newline at end of file
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
index 17e83c1..7a37b85 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/Utils.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.widget.Toast;
import android.content.ActivityNotFoundException;
@@ -68,7 +69,9 @@
static void showBugreportNotification(Context context, String msg, int notificationId) {
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- Notification notification = new Notification.Builder(context)
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_DEFAULT));
+ Notification notification = new Notification.Builder(context, TAG)
.setSmallIcon(R.drawable.icon)
.setContentTitle(context.getString(
R.string.device_owner_requesting_bugreport_tests))
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
index 9d9a4bc..0355cb4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -21,6 +21,8 @@
import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.ContentProviderOperation;
import android.content.OperationApplicationException;
import android.database.Cursor;
@@ -30,6 +32,7 @@
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.view.View;
@@ -47,6 +50,8 @@
extends InteractiveVerifierActivity {
private static final String TAG = "NoListenerAttentionVerifier";
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final String NOTIFICATION_CHANNEL_ID_NOISY = TAG + "/noisy";
private static final String ALICE = "Alice";
private static final String ALICE_PHONE = "+16175551212";
private static final String ALICE_EMAIL = "alice@_foo._bar";
@@ -106,6 +111,23 @@
return tests;
}
+ private void createChannels() {
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ mNm.createNotificationChannel(channel);
+ NotificationChannel noisyChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY,
+ NOTIFICATION_CHANNEL_ID_NOISY, NotificationManager.IMPORTANCE_HIGH);
+ noisyChannel.enableVibration(true);
+ noisyChannel.setSound(
+ Settings.System.DEFAULT_RINGTONE_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ mNm.createNotificationChannel(noisyChannel);
+ }
+
+ private void deleteChannels() {
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY);
+ }
+
// Tests
protected class InsertContactsTest extends InteractiveTestCase {
@@ -213,6 +235,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_URI, false, false);
status = READY;
// wait for notifications to move through the system
@@ -267,6 +290,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -305,6 +329,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_URI, false, false);
status = READY;
// wait for notifications to move through the system
@@ -358,6 +383,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -395,6 +421,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_URI, false, false);
status = READY;
// wait for notifications to move through the system
@@ -448,6 +475,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -462,6 +490,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_NONE, false, false);
status = READY;
// wait for notifications to move through the system
@@ -492,6 +521,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -506,6 +536,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_NONE, true, false);
status = READY;
// wait for notifications to move through the system
@@ -536,6 +567,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -552,6 +584,7 @@
@Override
void setUp() {
+ createChannels();
// send B & C noisy
sendNotifications(SEND_B | SEND_C, MODE_NONE, false, true);
status = READY;
@@ -601,6 +634,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -615,6 +649,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_NONE, true, false);
status = READY;
// wait for notifications to move through the system
@@ -668,6 +703,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -682,6 +718,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_URI, false, false);
status = READY;
// wait for notifications to move through the system
@@ -712,6 +749,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -726,6 +764,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_EMAIL, false, false);
status = READY;
// wait for notifications to move through the system
@@ -756,6 +795,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -770,6 +810,7 @@
@Override
void setUp() {
+ createChannels();
sendNotifications(MODE_PHONE, false, false);
status = READY;
// wait for notifications to move through the system
@@ -800,6 +841,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannels();
MockListener.resetListenerData(mContext);
delay();
}
@@ -826,39 +868,39 @@
int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
+ final String channelId = noisy ? NOTIFICATION_CHANNEL_ID_NOISY : NOTIFICATION_CHANNEL_ID;
+
if ((which & SEND_B) != 0) {
- Notification.Builder bob = new Notification.Builder(mContext)
+ Notification.Builder bob = new Notification.Builder(mContext, channelId)
.setContentTitle(BOB)
.setContentText(BOB)
.setSmallIcon(R.drawable.ic_stat_bob)
.setPriority(priorityB)
.setCategory(Notification.CATEGORY_MESSAGE)
.setWhen(whenB);
- bob.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
addPerson(uriMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
}
if ((which & SEND_C) != 0) {
- Notification.Builder charlie = new Notification.Builder(mContext)
- .setContentTitle(CHARLIE)
- .setContentText(CHARLIE)
- .setSmallIcon(R.drawable.ic_stat_charlie)
- .setPriority(priorityC)
- .setCategory(Notification.CATEGORY_MESSAGE)
- .setWhen(whenC);
- charlie.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
+ Notification.Builder charlie =
+ new Notification.Builder(mContext, channelId)
+ .setContentTitle(CHARLIE)
+ .setContentText(CHARLIE)
+ .setSmallIcon(R.drawable.ic_stat_charlie)
+ .setPriority(priorityC)
+ .setCategory(Notification.CATEGORY_MESSAGE)
+ .setWhen(whenC);
addPerson(uriMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
}
if ((which & SEND_A) != 0) {
- Notification.Builder alice = new Notification.Builder(mContext)
+ Notification.Builder alice = new Notification.Builder(mContext, channelId)
.setContentTitle(ALICE)
.setContentText(ALICE)
.setSmallIcon(R.drawable.ic_stat_alice)
.setPriority(priorityA)
.setCategory(Notification.CATEGORY_MESSAGE)
.setWhen(whenA);
- alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
addPerson(uriMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
index 36a502c..5150a26 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
@@ -40,7 +40,7 @@
import java.util.Set;
public class MockAssistant extends NotificationAssistantService {
- static final String TAG = "MockListener";
+ static final String TAG = "MockAssistant";
static final String SERVICE_BASE = "android.service.notification.cts.";
static final String SERVICE_CHECK = SERVICE_BASE + "SERVICE_CHECK";
@@ -318,8 +318,7 @@
}
@Override
- public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
- boolean user) {
+ public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
if (!mTestPackages.contains(sbn.getPackageName())) { return null; }
Log.d(TAG, "enqueued: " + sbn.getTag());
mEnqueued.add(sbn.getTag());
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
index 540240d..191d0f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
@@ -71,6 +71,9 @@
private static final String THIS_PKG = "com.android.cts.verifier";
private static final String OTHER_PKG = "android";
+ public static final String ORIGINAL_CHANNEL_ID = TAG + ": original";
+ public static final String NEW_CHANNEL_ID = TAG + ": new";
+
private String mTag1;
private String mTag2;
private String mTag3;
@@ -126,12 +129,7 @@
}
@SuppressLint("NewApi")
- private void sendNotifications() {
- sendNotifications(null);
- }
-
- @SuppressLint("NewApi")
- private void sendNotifications(NotificationChannel channel) {
+ private void sendNotifications(String channelId) {
mTag1 = UUID.randomUUID().toString();
mTag2 = UUID.randomUUID().toString();
mTag3 = UUID.randomUUID().toString();
@@ -152,7 +150,7 @@
mPackageString = "com.android.cts.verifier";
- Notification n1 = new Notification.Builder(mContext)
+ Notification n1 = new Notification.Builder(mContext, channelId)
.setContentTitle("One")
.setSortKey(Adjustment.KEY_CHANNEL_ID)
.setContentText(mTag1.toString())
@@ -161,13 +159,11 @@
.setWhen(mWhen1)
.setDeleteIntent(makeIntent(1, mTag1))
.setOnlyAlertOnce(true)
- .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
- : channel.getId())
.build();
mNm.notify(mTag1, mId1, n1);
mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
- Notification n2 = new Notification.Builder(mContext)
+ Notification n2 = new Notification.Builder(mContext, channelId)
.setContentTitle("Two")
.setSortKey(Adjustment.KEY_PEOPLE)
.setContentText(mTag2.toString())
@@ -176,13 +172,11 @@
.setWhen(mWhen2)
.setDeleteIntent(makeIntent(2, mTag2))
.setAutoCancel(true)
- .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
- : channel.getId())
.build();
mNm.notify(mTag2, mId2, n2);
mFlag2 = Notification.FLAG_AUTO_CANCEL;
- Notification n3 = new Notification.Builder(mContext)
+ Notification n3 = new Notification.Builder(mContext, channelId)
.setContentTitle("Three")
.setSortKey(Adjustment.KEY_SNOOZE_CRITERIA)
.setContentText(mTag3.toString())
@@ -192,13 +186,34 @@
.setDeleteIntent(makeIntent(3, mTag3))
.setAutoCancel(true)
.setOnlyAlertOnce(true)
- .setChannel(channel == null ? NotificationChannel.DEFAULT_CHANNEL_ID
- : channel.getId())
.build();
mNm.notify(mTag3, mId3, n3);
mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
}
+ private void createChannels() {
+ try {
+ NotificationChannel newChannel = new NotificationChannel(
+ NEW_CHANNEL_ID, NEW_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ mNm.createNotificationChannel(newChannel);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to create channel", e);
+ }
+ try {
+ NotificationChannel originalChannel = new NotificationChannel(ORIGINAL_CHANNEL_ID,
+ ORIGINAL_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ mNm.createNotificationChannel(originalChannel);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to create channel", e);
+ }
+ }
+
+ private void deleteChannels() {
+ mNm.cancelAll();
+ mNm.deleteNotificationChannel(ORIGINAL_CHANNEL_ID);
+ mNm.deleteNotificationChannel(NEW_CHANNEL_ID);
+ }
+
// Tests
protected class IsEnabledTest extends InteractiveTestCase {
@@ -278,13 +293,19 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
// wait for notifications to move through the system
delay();
}
@Override
+ void tearDown() {
+ deleteChannels();
+ }
+
+ @Override
void test() {
MockAssistant.probeListenerEnqueued(mContext,
new StringListResultCatcher() {
@@ -307,18 +328,23 @@
@Override
View inflate(ViewGroup parent) {
return createAutoItem(parent, R.string.nls_note_received);
-
}
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
// wait for notifications to move through the system
delay();
}
@Override
+ void tearDown() {
+ deleteChannels();
+ }
+
+ @Override
void test() {
MockAssistant.probeListenerPosted(mContext,
new StringListResultCatcher() {
@@ -422,7 +448,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -455,7 +482,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -470,7 +497,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -509,7 +537,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -524,7 +552,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -557,7 +586,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -634,7 +663,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -659,7 +689,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -675,7 +705,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -700,7 +731,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -710,10 +741,6 @@
private class AdjustNotificationTest extends InteractiveTestCase {
private ArrayList<String> people;
private ArrayList<SnoozeCriterion> snooze;
- private NotificationChannel originalChannel = new NotificationChannel("original", "new",
- NotificationManager.IMPORTANCE_LOW);
- private NotificationChannel newChannel = new NotificationChannel("new", "new",
- NotificationManager.IMPORTANCE_LOW);
private Map<String, Bundle> adjustments;
@Override
@@ -723,22 +750,13 @@
@Override
void setUp() {
- try {
- mNm.createNotificationChannel(newChannel);
- } catch (Exception e) {
- Log.e(TAG, "failed to create channel", e);
- }
- try {
- mNm.createNotificationChannel(originalChannel);
- } catch (Exception e) {
- Log.e(TAG, "failed to create channel", e);
- }
+ createChannels();
adjustments = getAdjustments();
snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
Adjustment.KEY_SNOOZE_CRITERIA);
people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
Adjustment.KEY_PEOPLE);
- sendNotifications(originalChannel);
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -785,7 +803,7 @@
String tag = payload.getString(KEY_TAG);
if (mTag1.equals(tag)) {
found.add(mTag1);
- pass &= checkEquals(newChannel.getId(),
+ pass &= checkEquals(NEW_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -800,7 +818,7 @@
+ "%s, %s)");
} else if (mTag2.equals(tag)) {
found.add(mTag2);
- pass &= checkEquals(originalChannel.getId(),
+ pass &= checkEquals(ORIGINAL_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -815,7 +833,7 @@
+ "%s, %s)");
} else if (mTag3.equals(tag)) {
found.add(mTag3);
- pass &= checkEquals(originalChannel.getId(),
+ pass &= checkEquals(ORIGINAL_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -845,9 +863,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
- mNm.deleteNotificationChannel(originalChannel.getId());
- mNm.deleteNotificationChannel(newChannel.getId());
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -857,10 +873,6 @@
private class AdjustEnqueuedNotificationTest extends InteractiveTestCase {
private ArrayList<String> people;
private ArrayList<SnoozeCriterion> snooze;
- private NotificationChannel originalChannel = new NotificationChannel("original", "new",
- NotificationManager.IMPORTANCE_LOW);
- private NotificationChannel newChannel = new NotificationChannel("new", "new",
- NotificationManager.IMPORTANCE_LOW);
private Map<String, Bundle> adjustments;
@Override
@@ -871,22 +883,13 @@
@Override
void setUp() {
MockAssistant.adjustEnqueue(mContext);
- try {
- mNm.createNotificationChannel(newChannel);
- } catch (Exception e) {
- Log.e(TAG, "failed to create channel", e);
- }
- try {
- mNm.createNotificationChannel(originalChannel);
- } catch (Exception e) {
- Log.e(TAG, "failed to create channel", e);
- }
+ createChannels();
adjustments = getAdjustments();
snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
Adjustment.KEY_SNOOZE_CRITERIA);
people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
Adjustment.KEY_PEOPLE);
- sendNotifications(originalChannel);
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -927,7 +930,7 @@
String tag = payload.getString(KEY_TAG);
if (mTag1.equals(tag)) {
found.add(mTag1);
- pass &= checkEquals(newChannel.getId(),
+ pass &= checkEquals(NEW_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -942,7 +945,7 @@
+ "%s, %s)");
} else if (mTag2.equals(tag)) {
found.add(mTag2);
- pass &= checkEquals(originalChannel.getId(),
+ pass &= checkEquals(ORIGINAL_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -957,7 +960,7 @@
+ "%s, %s)");
} else if (mTag3.equals(tag)) {
found.add(mTag3);
- pass &= checkEquals(originalChannel.getId(),
+ pass &= checkEquals(ORIGINAL_CHANNEL_ID,
((NotificationChannel) payload.getParcelable(
KEY_CHANNEL)).getId(),
"data integrity test: notification channel ("
@@ -987,9 +990,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
- mNm.deleteNotificationChannel(originalChannel.getId());
- mNm.deleteNotificationChannel(newChannel.getId());
+ deleteChannels();
sleep(1000);
MockAssistant.resetListenerData(mContext);
delay();
@@ -1123,8 +1124,18 @@
if (result == null || result.size() <= 1) {
status = PASS;
} else {
- logFail();
- status = FAIL;
+ boolean fail = false;
+ for (Parcelable payloadData : result) {
+ NotificationChannel payload =
+ (NotificationChannel) payloadData;
+ fail |= compareChannels(channel, payload);
+ }
+ if (fail) {
+ logFail();
+ status = FAIL;
+ } else {
+ status = PASS;
+ }
}
next();
}
@@ -1157,8 +1168,8 @@
@Override
void setUp() {
updatedChannel.setVibrationPattern(new long[] {467, 2478, 24738});
- updatedChannel.setSound(new Uri.Builder().appendPath("sound").build());
- updatedChannel.setLights(true);
+ updatedChannel.setSound(new Uri.Builder().appendPath("sound").build(), null);
+ updatedChannel.enableLights(true);
updatedChannel.enableVibration(true);
updatedChannel.setBypassDnd(true);
updatedChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
@@ -1243,7 +1254,7 @@
} catch (Exception e) {
Log.e(TAG, "failed to create channel", e);
}
- sendNotifications(channel);
+ sendNotifications(channel.getId());
status = READY;
delay(6000);
}
@@ -1308,7 +1319,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -1381,6 +1393,7 @@
mNm.cancel(mTag1, mId1);
mNm.cancel(mTag2, mId2);
mNm.cancel(mTag2, mId3);
+ deleteChannels();
MockAssistant.resetListenerData(mContext);
delay();
}
@@ -1399,7 +1412,8 @@
@Override
void setUp() {
- sendNotifications();
+ createChannels();
+ sendNotifications(ORIGINAL_CHANNEL_ID);
status = READY;
delay();
}
@@ -1457,7 +1471,7 @@
@Override
void tearDown() {
- mNm.cancelAll();
+ deleteChannels();
MockAssistant.resetListenerData(mContext);
delay();
}
@@ -1481,6 +1495,7 @@
pass &= checkEquals(expected.getSound(), actual.getSound(), msg);
pass &= checkEquals(expected.canBypassDnd(), actual.canBypassDnd(), msg);
pass &= checkEquals(expected.getVibrationPattern(), actual.getVibrationPattern(), msg);
+ pass &= checkEquals(expected.getAudioAttributes(), actual.getAudioAttributes(), msg);
return pass;
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 38fc5bd..425b36f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -19,6 +19,8 @@
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.provider.Settings.Secure;
import android.util.Log;
import android.view.View;
@@ -40,6 +42,7 @@
public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
implements Runnable {
private static final String TAG = "NoListenerVerifier";
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
private String mTag1;
private String mTag2;
@@ -91,6 +94,16 @@
return tests;
}
+ private void createChannel() {
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ mNm.createNotificationChannel(channel);
+ }
+
+ private void deleteChannel() {
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ }
+
@SuppressLint("NewApi")
private void sendNotifications() {
mTag1 = UUID.randomUUID().toString();
@@ -113,7 +126,7 @@
mPackageString = "com.android.cts.verifier";
- Notification n1 = new Notification.Builder(mContext)
+ Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("ClearTest 1")
.setContentText(mTag1.toString())
.setPriority(Notification.PRIORITY_LOW)
@@ -125,7 +138,7 @@
mNm.notify(mTag1, mId1, n1);
mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
- Notification n2 = new Notification.Builder(mContext)
+ Notification n2 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("ClearTest 2")
.setContentText(mTag2.toString())
.setPriority(Notification.PRIORITY_HIGH)
@@ -137,7 +150,7 @@
mNm.notify(mTag2, mId2, n2);
mFlag2 = Notification.FLAG_AUTO_CANCEL;
- Notification n3 = new Notification.Builder(mContext)
+ Notification n3 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("ClearTest 3")
.setContentText(mTag3.toString())
.setPriority(Notification.PRIORITY_LOW)
@@ -162,6 +175,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
// wait for notifications to move through the system
@@ -169,6 +183,11 @@
}
@Override
+ void tearDown() {
+ deleteChannel();
+ }
+
+ @Override
void test() {
MockListener.probeListenerPosted(mContext,
new MockListener.StringListResultCatcher() {
@@ -276,6 +295,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
delay();
@@ -310,6 +330,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -323,6 +344,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
delay();
@@ -368,6 +390,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockAssistant.resetListenerData(mContext);
delay();
}
@@ -381,6 +404,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
delay();
@@ -415,6 +439,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -500,6 +525,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
delay();
@@ -526,6 +552,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -678,6 +705,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
state = READY_TO_SNOOZE;
@@ -747,6 +775,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -765,6 +794,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
state = READY_TO_SNOOZE;
@@ -837,6 +867,7 @@
@Override
void tearDown() {
mNm.cancelAll();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
index b40ecc6..150c21f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -17,6 +17,8 @@
package com.android.cts.verifier.notifications;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
@@ -39,6 +41,7 @@
private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
private static final String EXTRA_ID = "ID";
private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+ private static final String NOTIFICATION_CHANNEL_ID = "PackagePriorityVerifierActivity";
static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
private CharSequence mAppLabel;
@@ -68,6 +71,16 @@
return tests;
}
+ private void createChannel() {
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+ mNm.createNotificationChannel(channel);
+ }
+
+ private void deleteChannel() {
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ }
+
// Tests
/** Make sure the helper package is installed. */
@@ -155,6 +168,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
// wait for notifications to move through the system
@@ -184,6 +198,7 @@
@Override
void tearDown() {
cancelNotifications();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -200,6 +215,7 @@
@Override
void setUp() {
+ createChannel();
sendNotifications();
status = READY;
// wait for notifications to move through the system
@@ -229,6 +245,7 @@
@Override
void tearDown() {
cancelNotifications();
+ deleteChannel();
MockListener.resetListenerData(mContext);
delay();
}
@@ -237,7 +254,7 @@
private void sendNotifications() {
// post ours first, with an explicit time in the past to avoid any races.
- Notification.Builder alice = new Notification.Builder(mContext)
+ Notification.Builder alice = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("alice title")
.setContentText("alice content")
.setSmallIcon(R.drawable.ic_stat_alice)
@@ -246,7 +263,7 @@
mNm.notify(0, alice.build());
// then post theirs, so it should be higher by default due to recency
- Notification.Builder bob = new Notification.Builder(mContext)
+ Notification.Builder bob = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("bob title")
.setContentText("bob content")
.setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
deleted file mode 100644
index 5633c16..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2010 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.cts.verifier.os;
-
-/** Bits and pieces copied from hidden API of android.os.FileUtils. */
-public class FileUtils {
-
- private static final int S_IFSOCK = 0140000;
- private static final int S_IFLNK = 0120000;
- private static final int S_IFREG = 0100000;
- private static final int S_IFBLK = 0060000;
- private static final int S_IFDIR = 0040000;
- private static final int S_IFCHR = 0020000;
- private static final int S_IFIFO = 0010000;
-
- private static final int S_ISUID = 0004000;
- private static final int S_ISGID = 0002000;
- private static final int S_ISVTX = 0001000;
-
- private static final int S_IRUSR = 00400;
- private static final int S_IWUSR = 00200;
- private static final int S_IXUSR = 00100;
-
- private static final int S_IRGRP = 00040;
- private static final int S_IWGRP = 00020;
- private static final int S_IXGRP = 00010;
-
- private static final int S_IROTH = 00004;
- private static final int S_IWOTH = 00002;
- private static final int S_IXOTH = 00001;
-
- static {
- System.loadLibrary("ctsverifier_jni");
- }
-
- public static class FileStatus {
-
- private int dev;
- private int ino;
- private int mode;
- private int nlink;
- private int uid;
- private int gid;
- private int rdev;
- private long size;
- private int blksize;
- private long blocks;
- private long atime;
- private long mtime;
- private long ctime;
- private boolean executable;
-
- public int getUid() {
- return uid;
- }
-
- public int getGid() {
- return gid;
- }
-
- public int getMode() {
- return mode;
- }
-
- public boolean isDirectory() {
- return hasModeFlag(mode, S_IFDIR);
- }
-
- public boolean isSymbolicLink() {
- return hasModeFlag(mode, S_IFLNK);
- }
-
- public boolean isSetUid() {
- return hasModeFlag(mode, S_ISUID);
- }
-
- public boolean isSetGid() {
- return hasModeFlag(mode, S_ISGID);
- }
-
- public boolean isExecutableByCTS() {
- return executable;
- }
- }
-
- /**
- * @param path of the file to stat
- * @param status object to set the fields on
- * @param statLinks or don't stat links (lstat vs stat)
- * @return whether or not we were able to stat the file
- */
- public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
-
- public native static String getUserName(int uid);
-
- public native static String getGroupName(int gid);
-
- /** Display the file's mode like "ls -l" does. */
- public static String getFormattedPermissions(int mode) {
- StringBuilder permissions = new StringBuilder("-rwxrwxrwx");
-
- int[] typeMasks = {S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR, S_IFIFO};
- char[] typeSymbols = {'s', 'l', '-', 'b', 'd', 'c', 'p'};
- for (int i = 0; i < typeMasks.length; i++) {
- if (hasModeFlag(mode, typeMasks[i])) {
- permissions.setCharAt(0, typeSymbols[i]);
- break;
- }
- }
-
- int[] masks = {S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP,
- S_IROTH, S_IWOTH, S_IXOTH};
- for (int i = 0; i < masks.length; i++) {
- if (!hasModeFlag(mode, masks[i])) {
- permissions.setCharAt(1 + i, '-');
- }
- }
-
-
- if (hasModeFlag(mode, S_ISUID)) {
- permissions.setCharAt(3, hasModeFlag(mode, S_IXUSR) ? 's' : 'S');
- }
-
- if (hasModeFlag(mode, S_ISGID)) {
- permissions.setCharAt(6, hasModeFlag(mode, S_IXGRP) ? 's' : 'S');
- }
-
- if (hasModeFlag(mode, S_ISVTX)) {
- permissions.setCharAt(9, hasModeFlag(mode, S_IXOTH) ? 't' : 'T');
- }
-
- return permissions.toString();
- }
-
- private static boolean hasModeFlag(int mode, int flag) {
- return (mode & flag) == flag;
- }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
index ab8d084..0e01fa1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/sixdof/Activities/StartActivity.java
@@ -19,9 +19,12 @@
import com.android.cts.verifier.R;
import com.android.cts.verifier.sensors.sixdof.Utils.ReportExporter;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.app.Activity;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
@@ -53,6 +56,12 @@
startPhase1();
}
});
+
+ // If there is no 6DoF sensor advertised, pass trivially.
+ SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
+ if (sensorManager.getDefaultSensor(Sensor.TYPE_POSE_6DOF) == null) {
+ StartActivity.this.setTestResultAndFinish(true);
+ }
}
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
index 936775e..1ea7608 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
@@ -192,8 +192,7 @@
assertEquals(MAX_BUFFER_SIZE, numRead);
assertArrayEquals(origBufferMax, bufferMax);
- // Send two transfers in a row
- nextTest(is, os, "measure transfer speed");
+ nextTest(is, os, "measure out transfer speed");
byte[] result = new byte[1];
long bytesSent = 0;
@@ -209,8 +208,32 @@
assertEquals(1, result[0]);
// We don't mandate min speed for now, let's collect data on what it is.
getReportLog().setSummary(
- "Speed", speedKBPS, ResultType.HIGHER_BETTER, ResultUnit.KBPS);
- Log.i(LOG_TAG, "Data transfer speed is " + speedKBPS + "KBPS");
+ "Output USB accesory transfer speed",
+ speedKBPS,
+ ResultType.HIGHER_BETTER,
+ ResultUnit.KBPS);
+ Log.i(LOG_TAG, "Write data transfer speed is " + speedKBPS + "KBPS");
+
+ nextTest(is, os, "measure in transfer speed");
+
+ long bytesRead = 0;
+ timeStart = SystemClock.elapsedRealtime();
+ while (bytesRead < TEST_DATA_SIZE_THRESHOLD) {
+ numRead = is.read(bufferMax);
+ bytesRead += numRead;
+ }
+ numRead = is.read(result);
+ speedKBPS = (bytesRead * 8 * 1000. / 1024.)
+ / (SystemClock.elapsedRealtime() - timeStart);
+ assertEquals(1, numRead);
+ assertEquals(1, result[0]);
+ // We don't mandate min speed for now, let's collect data on what it is.
+ getReportLog().setSummary(
+ "Input USB accesory transfer speed",
+ speedKBPS,
+ ResultType.HIGHER_BETTER,
+ ResultUnit.KBPS);
+ Log.i(LOG_TAG, "Read data transfer speed is " + speedKBPS + "KBPS");
nextTest(is, os, "done");
}
diff --git a/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java b/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
index 5999d3a..5683b32 100644
--- a/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
+++ b/apps/CtsVerifierUSBCompanion/src/com/android/cts/verifierusbcompanion/AccessoryTestCompanion.java
@@ -164,7 +164,7 @@
}
break;
- case "measure transfer speed": {
+ case "measure out transfer speed": {
byte[] buffer = new byte[MAX_BUFFER_SIZE];
long bytesRead = 0;
@@ -174,9 +174,27 @@
bytesRead += numTransferred;
}
- // If the data length is a multple of the maxpacket size read zero packet.
- int numTransferred = connection.bulkTransfer(in, buffer, 1, 0);
- assertEquals(0, numTransferred);
+ // If the data length is a multple of the maxpacket size try reading zero
+ // length packet. On some implementation it might be missing.
+ connection.bulkTransfer(in, buffer, 1, 100);
+
+ byte[] confirm = new byte[] {1};
+ int numTransferred = connection.bulkTransfer(out, confirm, 1, 0);
+ assertEquals(1, numTransferred);
+ }
+ break;
+
+ case "measure in transfer speed": {
+ byte[] buffer = new byte[MAX_BUFFER_SIZE];
+
+ long bytesWritten = 0;
+ int numTransferred = 0;
+ while (bytesWritten < TEST_DATA_SIZE_THRESHOLD) {
+ numTransferred =
+ connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
+ assertEquals(MAX_BUFFER_SIZE, numTransferred);
+ bytesWritten += numTransferred;
+ }
byte[] confirm = new byte[] {1};
numTransferred = connection.bulkTransfer(out, confirm, 1, 0);
@@ -192,9 +210,9 @@
0);
assertEquals(MAX_BUFFER_SIZE, numTransferred);
- // If the data length is a multple of the maxpacket size read zero packet.
- numTransferred = connection.bulkTransfer(in, empty, 1, 0);
- assertEquals(0, numTransferred);
+ // If the data length is a multple of the maxpacket size try reading zero
+ // length packet. On some implementation it might be missing.
+ connection.bulkTransfer(in, empty, 1, 100);
numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
assertEquals(MAX_BUFFER_SIZE, numTransferred);
@@ -214,11 +232,11 @@
MAX_BUFFER_SIZE, 0);
assertEquals(MAX_BUFFER_SIZE, numTransferred);
- // If the data length is a multple of the maxpacket size read zero packet.
- numTransferred = connection.bulkTransfer(in, empty, 1, 0);
- assertEquals(0, numTransferred);
+ // If the data length is a multple of the maxpacket size try reading zero
+ // length packet. On some implementation it might be missing.
+ connection.bulkTransfer(in, empty, 1, 100);
- numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0);
+ numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 100);
assertEquals(MAX_BUFFER_SIZE, numTransferred);
numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE,
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 746b840..0fb1d65 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -17,6 +17,7 @@
import android.app.Notification;
import android.app.Notification.Action;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -35,6 +36,7 @@
public class NotificationBot extends BroadcastReceiver {
private static final String TAG = "NotificationBot";
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
private static final String EXTRA_ID = "ID";
private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
@@ -133,14 +135,19 @@
final RemoteInput ri = new RemoteInput.Builder("result")
.setLabel("Type something here and press send button").build();
- final Notification.Builder nb = new Notification.Builder(context)
+ NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ final Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE))
.setSmallIcon(android.R.drawable.ic_popup_sync)
.addAction(new Action.Builder(0,
"Type something here and press send button", receiverIntent)
.addRemoteInput(ri)
.build());
- context.getSystemService(NotificationManager.class).notify(0, nb.build());
+ notificationManager.notify(0, nb.build());
}
/**
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index 06e4de5..6a1b7dc 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -23,7 +23,8 @@
compatibility-common-util-devicesidelib \
android-support-test \
ub-uiautomator \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ legacy-android-test
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
index 5b5dc81..0662e81 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
@@ -423,7 +423,8 @@
int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2;
int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2;
- emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, extraWaitMs);
+ emulateLongPressOnScreen(
+ instrumentation, xOnScreen, yOnScreen, touchSlop, extraWaitMs, true);
}
/**
@@ -444,7 +445,24 @@
int xOnScreen = viewOnScreenXY[0] + offsetX;
int yOnScreen = viewOnScreenXY[1] + offsetY;
- emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, 0);
+ emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, 0, true);
+ }
+
+ /**
+ * Emulates a long press then a linear drag gesture between 2 points across the screen.
+ * This is used for drag selection.
+ *
+ * @param instrumentation the instrumentation used to run the test
+ * @param dragStartX Start X of the emulated drag gesture
+ * @param dragStartY Start Y of the emulated drag gesture
+ * @param dragAmountX X amount of the emulated drag gesture
+ * @param dragAmountY Y amount of the emulated drag gesture
+ */
+ public static void emulateLongPressAndDragGesture(Instrumentation instrumentation,
+ int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
+ emulateLongPressOnScreen(instrumentation, dragStartX, dragStartY,
+ 0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */);
+ emulateDragGesture(instrumentation, dragStartX, dragStartY, dragAmountX, dragAmountY);
}
/**
@@ -455,16 +473,19 @@
* @param yOnScreen Y position on screen for the "long press"
* @param extraWaitMs extra duration of emulated long press in milliseconds added
* after the system-level "long press" timeout.
+ * @param upGesture whether to include an up event.
*/
private static void emulateLongPressOnScreen(Instrumentation instrumentation,
- int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs) {
+ int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) {
final UiAutomation uiAutomation = instrumentation.getUiAutomation();
final long downTime = SystemClock.uptimeMillis();
injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen);
injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs);
- injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen);
+ if (upGesture) {
+ injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen);
+ }
// Wait for the system to process all events in the queue
instrumentation.waitForIdleSync();
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
index 3711455..1a1ec19 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -16,6 +16,7 @@
package com.android.compatibility.common.util;
+import android.os.Build;
import android.os.SystemProperties;
/**
@@ -30,17 +31,29 @@
*/
public static String FIRST_API_LEVEL = "ro.product.first_api_level";
- /** Value to be returned by SystemProperties.getInt() if property is not found */
+ /** Value to be returned by getPropertyInt() if property is not found */
public static int INT_VALUE_IF_UNSET = -1;
/** Returns whether the device build is the factory ROM */
public static boolean isFactoryROM() {
// property should be undefined if and only if the product is factory ROM.
- return getFirstApiLevel() == INT_VALUE_IF_UNSET;
+ return getPropertyInt(FIRST_API_LEVEL) == INT_VALUE_IF_UNSET;
}
- /** Return value of read-only property "ro.product.first_api_level" */
+ /**
+ * Return the first API level for this product. If the read-only property is unset,
+ * this means the first API level is the current API level, and the current API level
+ * is returned.
+ */
public static int getFirstApiLevel() {
- return SystemProperties.getInt(FIRST_API_LEVEL, INT_VALUE_IF_UNSET);
+ int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
+ return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
+ }
+
+ /**
+ * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
+ */
+ public static int getPropertyInt(String property) {
+ return SystemProperties.getInt(property, INT_VALUE_IF_UNSET);
}
}
diff --git a/common/host-side/tradefed/res/config/metadata-config.xml b/common/host-side/tradefed/res/config/metadata-config.xml
new file mode 100644
index 0000000..37f1a3e
--- /dev/null
+++ b/common/host-side/tradefed/res/config/metadata-config.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Metadata result reporter for Compatibility suites">
+ <result_reporter class="com.android.compatibility.common.tradefed.result.MetadataReporter" />
+</configuration>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
index c4a1385..4944da2 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
@@ -21,6 +21,7 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.IShardableListener;
import com.android.tradefed.util.TimeUtil;
@@ -50,13 +51,15 @@
* {@inheritDoc}
*/
@Override
- public void invocationStarted(IBuildInfo buildInfo) {
- if (buildInfo == null) {
- CLog.w("buildInfo should not be null");
+ public void invocationStarted(IInvocationContext context) {
+ if (context == null) {
+ CLog.w("InvocationContext should not be null");
return;
}
+ IBuildInfo primaryBuild = context.getBuildInfos().get(0);
+
// Escape any "%" signs in the device serial.
- mDeviceSerial = buildInfo.getDeviceSerial().replace("%", "%%");
+ mDeviceSerial = primaryBuild.getDeviceSerial().replace("%", "%%");
}
/**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
new file mode 100644
index 0000000..a3599d8f
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/MetadataReporter.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.result;
+
+import com.android.json.stream.JsonWriter;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
+import com.android.compatibility.common.util.ResultHandler;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.IShardableListener;
+import com.android.tradefed.result.StubTestInvocationListener;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.TimeUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * Write test metadata to the result/metadata folder.
+ */
+public class MetadataReporter extends StubTestInvocationListener implements IShardableListener {
+
+ @Option(name = "include-failure-time", description = "Include timing about tests that failed.")
+ private boolean mIncludeFailures = false;
+
+ @Option(name = "min-test-duration-sec", description = "Ignore test durations less than this.")
+ private int mMinTestDurationSec = 2;
+
+ @Option(name = CompatibilityTest.RETRY_OPTION,
+ shortName = 'r',
+ description = "retry a previous session.",
+ importance = Importance.IF_UNSET)
+ private Integer mRetrySessionId = null;
+
+ private static final String METADATA_DIR = "metadata";
+ private CompatibilityBuildHelper mBuildHelper;
+ private File mMetadataDir;
+ private long mStartTime;
+ private String mCurrentModule;
+ private boolean mTestFailed;
+ private Collection<TestMetadata> mTestMetadata = new LinkedList<>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IShardableListener clone() {
+ MetadataReporter clone = new MetadataReporter();
+ OptionCopier.copyOptionsNoThrow(this, clone);
+ return clone;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void invocationStarted(IInvocationContext context) {
+ IBuildInfo buildInfo = context.getBuildInfos().get(0);
+ synchronized(this) {
+ if (mBuildHelper == null) {
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ try {
+ mMetadataDir = new File(mBuildHelper.getResultDir(), METADATA_DIR);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Metadata Directory was not created: " +
+ mMetadataDir.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String id, int numTests) {
+ this.mCurrentModule = id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testStarted(TestIdentifier test) {
+ mStartTime = System.currentTimeMillis();
+ mTestFailed = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testFailed(TestIdentifier test, String trace) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testIgnored(TestIdentifier test) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testAssumptionFailure(TestIdentifier test, String trace) {
+ mTestFailed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ long duration = (System.currentTimeMillis() - mStartTime) / 1000;
+ if (mTestFailed && !mIncludeFailures) {
+ return;
+ }
+ if (duration < mMinTestDurationSec) {
+ return;
+ }
+
+ TestMetadata metadata = new TestMetadata();
+ metadata.testId = buildTestId(test);
+ metadata.seconds = duration;
+ mTestMetadata.add(metadata);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+ if (!mTestMetadata.isEmpty()) {
+ tryWriteToFile(mBuildHelper, mCurrentModule, mMetadataDir, mTestMetadata);
+ }
+ mTestMetadata.clear();
+ }
+
+ /** Information about a test's execution. */
+ public static class TestMetadata {
+ // The id of the test
+ String testId;
+ // The duration of the test.
+ long seconds;
+ }
+
+ private static String buildTestId(TestIdentifier test) {
+ return String.format("%s.%s", test.getClassName(), test.getTestName());
+ }
+
+ private static void tryWriteToFile(
+ CompatibilityBuildHelper compatibilityBuildHelper,
+ String moduleName,
+ File metadataDir,
+ Collection<TestMetadata> metadatas) {
+
+ metadataDir.mkdirs();
+
+ String moduleFileName = moduleName + "." + System.currentTimeMillis() + ".json";
+ File metadataFile = new File(metadataDir, moduleFileName);
+ Map<String, String> buildAttributes =
+ compatibilityBuildHelper.getBuildInfo().getBuildAttributes();
+ try (JsonWriter writer = new JsonWriter(new PrintWriter(metadataFile))) {
+ writer.beginObject();
+
+ writer.name("fingerprint");
+ writer.value(buildAttributes.get("cts:build_fingerprint"));
+
+ writer.name("product");
+ writer.value(buildAttributes.get("cts:build_product"));
+
+ writer.name("build_id");
+ writer.value(buildAttributes.get("cts:build_id"));
+
+ writer.name("suite_version");
+ writer.value(compatibilityBuildHelper.getSuiteVersion());
+
+ writer.name("suite_name");
+ writer.value(compatibilityBuildHelper.getSuiteName());
+
+ writer.name("suite_build");
+ writer.value(compatibilityBuildHelper.getSuiteBuild());
+
+ writer.name("module_id");
+ writer.value(moduleName);
+
+ writer.name("test");
+ writer.beginArray();
+ for (TestMetadata metadata : metadatas) {
+ writer.beginObject();
+ writer.name("id");
+ writer.value(metadata.testId);
+ writer.name("sec");
+ writer.value(metadata.seconds);
+ writer.endObject();
+ }
+ writer.endArray();
+
+ writer.endObject();
+ } catch (IOException e) {
+ CLog.e("[%s] While saving metadata.", metadataFile.getAbsolutePath());
+ CLog.e(e);
+ }
+ }
+
+ protected Collection<TestMetadata> getTestMetadata() {
+ return Collections.unmodifiableCollection(mTestMetadata);
+ }
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
index f3ae7a3..8140887 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ModuleListener.java
@@ -17,7 +17,7 @@
import com.android.compatibility.common.tradefed.testtype.IModuleDef;
import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
@@ -49,9 +49,9 @@
* {@inheritDoc}
*/
@Override
- public void invocationStarted(IBuildInfo buildInfo) {
- CLog.d("ModuleListener.invocationStarted(%s)", buildInfo.toString());
- mListener.invocationStarted(buildInfo);
+ public void invocationStarted(IInvocationContext context) {
+ CLog.d("ModuleListener.invocationStarted(%s)", context.getBuildInfos());
+ mListener.invocationStarted(context);
}
/**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index dc31b4e..878df6f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -16,8 +16,6 @@
package com.android.compatibility.common.tradefed.result;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
-import com.android.compatibility.common.tradefed.result.TestRunHandler;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTest.RetryType;
import com.android.compatibility.common.util.ICaseResult;
@@ -37,6 +35,7 @@
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.config.OptionCopier;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ILogSaverListener;
@@ -137,6 +136,8 @@
// to the master.
private final ResultReporter mMasterResultReporter;
+ private LogFileSaver mTestLogSaver;
+
/**
* Default constructor.
*/
@@ -156,34 +157,35 @@
* {@inheritDoc}
*/
@Override
- public void invocationStarted(IBuildInfo buildInfo) {
+ public void invocationStarted(IInvocationContext context) {
+ IBuildInfo primaryBuild = context.getBuildInfos().get(0);
synchronized(this) {
if (mBuildHelper == null) {
- mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ mBuildHelper = new CompatibilityBuildHelper(primaryBuild);
}
- if (mDeviceSerial == null && buildInfo.getDeviceSerial() != null) {
- mDeviceSerial = buildInfo.getDeviceSerial();
+ if (mDeviceSerial == null && primaryBuild.getDeviceSerial() != null) {
+ mDeviceSerial = primaryBuild.getDeviceSerial();
}
mCanMarkDone = canMarkDone(mBuildHelper.getRecentCommandLineArgs());
}
if (isShardResultReporter()) {
// Shard ResultReporters forward invocationStarted to the mMasterResultReporter
- mMasterResultReporter.invocationStarted(buildInfo);
+ mMasterResultReporter.invocationStarted(context);
return;
}
// NOTE: Everything after this line only applies to the master ResultReporter.
synchronized(this) {
- if (buildInfo.getDeviceSerial() != null) {
+ if (primaryBuild.getDeviceSerial() != null) {
// The master ResultReporter collects all device serials being used
// for the current implementation.
- mMasterDeviceSerials.add(buildInfo.getDeviceSerial());
+ mMasterDeviceSerials.add(primaryBuild.getDeviceSerial());
}
// The master ResultReporter collects all buildInfos.
- mMasterBuildInfos.add(buildInfo);
+ mMasterBuildInfos.add(primaryBuild);
if (mResultDir == null) {
// For the non-sharding case, invocationStarted is only called once,
@@ -232,7 +234,7 @@
mLogDir = new File(mBuildHelper.getLogsDir(),
CompatibilityBuildHelper.getDirSuffix(mBuildHelper.getStartTime()));
} catch (FileNotFoundException e) {
- e.printStackTrace();
+ CLog.e(e);
}
if (mLogDir != null && mLogDir.mkdirs()) {
debug("Created log dir %s", mLogDir.getAbsolutePath());
@@ -241,6 +243,9 @@
throw new IllegalArgumentException(String.format("Could not create log dir %s",
mLogDir.getAbsolutePath()));
}
+ if (mTestLogSaver == null) {
+ mTestLogSaver = new LogFileSaver(mLogDir);
+ }
}
/**
@@ -490,6 +495,11 @@
mBuildHelper.getSuiteBuild(), mResult, mResultDir, startTime,
elapsedTime + startTime, mReferenceUrl, getLogUrl(),
mBuildHelper.getCommandLineArgs());
+ if (mRetrySessionId != null) {
+ copyRetryFiles(ResultHandler.getResultDirectory(
+ mBuildHelper.getResultsDir(), mRetrySessionId), mResultDir);
+ }
+ File zippedResults = zipResults(mResultDir);
// Create failure report after zip file so extra data is not uploaded
File failureReport = ResultHandler.createFailureReport(resultFile);
if (failureReport.exists()) {
@@ -497,7 +507,6 @@
} else {
info("Test Result: %s", resultFile.getCanonicalPath());
}
- File zippedResults = zipResults(mResultDir);
debug("Full Result: %s", zippedResults.getCanonicalPath());
saveLog(resultFile, zippedResults);
@@ -538,12 +547,11 @@
return;
}
try {
- LogFileSaver saver = new LogFileSaver(mLogDir);
- File logFile = saver.saveAndZipLogData(name, type, stream.createInputStream());
+ File logFile = mTestLogSaver.saveAndZipLogData(name, type, stream.createInputStream());
debug("Saved logs for %s in %s", name, logFile.getAbsolutePath());
} catch (IOException e) {
warn("Failed to write log for %s", name);
- e.printStackTrace();
+ CLog.e(e);
}
}
@@ -714,6 +722,33 @@
}
/**
+ * Recursively copy any other files found in the previous session's result directory to the
+ * new result directory, so long as they don't already exist. For example, a "screenshots"
+ * directory generated in a previous session by a passing test will not be generated on retry
+ * unless copied from the old result directory.
+ *
+ * @param oldResultsDir
+ * @param newResultsDir
+ */
+ static void copyRetryFiles(File oldResultsDir, File newResultsDir) {
+ File[] oldFiles = oldResultsDir.listFiles();
+ for (File oldFile : oldFiles) {
+ File newFile = new File (newResultsDir, oldFile.getName());
+ if (!newFile.exists()) {
+ try {
+ if (oldFile.isDirectory()) {
+ FileUtil.recursiveCopy(oldFile, newFile);
+ } else {
+ FileUtil.copyFile(oldFile, newFile);
+ }
+ } catch (IOException e) {
+ warn("Failed to copy file \"%s\" from previous session", oldFile.getName());
+ }
+ }
+ }
+ }
+
+ /**
* Zip the contents of the given results directory.
*
* @param resultsDir
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index a10c3b5..7256437 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -41,7 +41,6 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.suite.checker.ISystemStatusChecker;
import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver;
import com.android.tradefed.testtype.Abi;
@@ -70,7 +69,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -457,17 +455,11 @@
if (checkers != null && !checkers.isEmpty()) {
runPreModuleCheck(module.getName(), checkers, mDevice, listener);
}
- // Workaround to b/34202787: Add result forwarder that ensures module is reported
- // with 0 tests if test runner doesn't report anything in this case.
- // Necessary for solution to b/33289177, in which completed modules may sometimes
- // not be marked done until retried with 0 tests.
- ModuleResultForwarder moduleListener = new ModuleResultForwarder(listener);
try {
if (module.getTest() instanceof IBuildReceiver) {
((IBuildReceiver)module.getTest()).setBuild(mBuildHelper.getBuildInfo());
}
- module.run(moduleListener);
- moduleListener.finish(module.getId());
+ module.run(listener);
} catch (DeviceUnresponsiveException due) {
// being able to catch a DeviceUnresponsiveException here implies that recovery
// was successful, and test execution should proceed to next module
@@ -810,32 +802,4 @@
public void setCollectTestsOnly(boolean collectTestsOnly) {
mCollectTestsOnly = collectTestsOnly;
}
-
- private class ModuleResultForwarder extends ResultForwarder {
-
- private boolean mTestRunStarted = false;
- private ITestInvocationListener mListener;
-
- public ModuleResultForwarder(ITestInvocationListener listener) {
- super(listener);
- mListener = listener;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void testRunStarted(String name, int numTests) {
- mListener.testRunStarted(name, numTests);
- mTestRunStarted = true;
- }
-
- public void finish(String moduleId) {
- if (!mTestRunStarted) {
- mListener.testRunStarted(moduleId, 0);
- mListener.testRunEnded(0, Collections.emptyMap());
- }
- }
- }
-
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
index be4308c..d4f73aab 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
@@ -19,7 +19,6 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
-import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
@@ -74,13 +73,9 @@
*/
@Override
protected HostTest createHostTest(Class<?> classObj) {
- JarHostTest test = new JarHostTest();
- OptionCopier.copyOptionsNoThrow(this, test);
- if (classObj != null) {
- test.setClassName(classObj.getName());
- } else {
- test.mJars.clear();
- }
+ JarHostTest test = (JarHostTest) super.createHostTest(classObj);
+ // clean the jar option since we are loading directly from classes after.
+ test.mJars = new HashSet<>();
return test;
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index 8566d5a..29009f1 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -27,6 +27,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.targetprep.BuildError;
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.ITargetPreparer;
@@ -218,8 +219,6 @@
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
- IModuleListener moduleListener = new ModuleListener(this, listener);
-
CLog.d("Running module %s", toString());
// Run DynamicConfigPusher setup once more, in case cleaner has previously
// removed dynamic config file from the target (see b/32877809)
@@ -242,7 +241,11 @@
((IDeviceTest) mTest).setDevice(mDevice);
}
- mTest.run(moduleListener);
+ IModuleListener moduleListener = new ModuleListener(this, listener);
+ // Guarantee events testRunStarted and testRunEnded in case underlying test runner does not
+ ModuleFinisher moduleFinisher = new ModuleFinisher(moduleListener);
+ mTest.run(moduleFinisher);
+ moduleFinisher.finish();
// Tear down
for (ITargetCleaner cleaner : mCleaners) {
@@ -318,4 +321,37 @@
public void setCollectTestsOnly(boolean collectTestsOnly) {
((ITestCollector) mTest).setCollectTestsOnly(collectTestsOnly);
}
+
+ /*
+ * ResultForwarder that tracks whether method testRunStarted() has been called for its
+ * listener. If not, invoking finish() will call testRunStarted with 0 tests for this module,
+ * as well as testRunEnded with 0 ms elapsed.
+ */
+ private class ModuleFinisher extends ResultForwarder {
+
+ private boolean mFinished;
+ private ITestInvocationListener mListener;
+
+ public ModuleFinisher(ITestInvocationListener listener) {
+ super(listener);
+ mListener = listener;
+ mFinished = false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void testRunStarted(String name, int numTests) {
+ mListener.testRunStarted(name, numTests);
+ mFinished = true;
+ }
+
+ public void finish() {
+ if (!mFinished) {
+ mListener.testRunStarted(mId, 0);
+ mListener.testRunEnded(0, Collections.emptyMap());
+ }
+ }
+ }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index 09d1219..3402757 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -15,9 +15,9 @@
*/
package com.android.compatibility.common.tradefed.testtype;
-import com.android.compatibility.common.tradefed.util.LinearPartition;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.tradefed.result.TestRunHandler;
+import com.android.compatibility.common.tradefed.util.LinearPartition;
import com.android.compatibility.common.util.TestFilter;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.ConfigurationException;
diff --git a/common/host-side/tradefed/tests/res/testtype/testJar2.jar b/common/host-side/tradefed/tests/res/testtype/testJar2.jar
new file mode 100644
index 0000000..4433986
--- /dev/null
+++ b/common/host-side/tradefed/tests/res/testtype/testJar2.jar
Binary files differ
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 4484d49..57e2b9b 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -21,6 +21,7 @@
import com.android.compatibility.common.tradefed.presubmit.PresubmitSetupValidation;
import com.android.compatibility.common.tradefed.result.ChecksumReporterTest;
import com.android.compatibility.common.tradefed.result.ConsoleReporterTest;
+import com.android.compatibility.common.tradefed.result.MetadataReporterTest;
import com.android.compatibility.common.tradefed.result.ResultReporterTest;
import com.android.compatibility.common.tradefed.result.SubPlanCreatorTest;
import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
@@ -65,6 +66,12 @@
addTestSuite(SubPlanCreatorTest.class);
// targetprep
+ addTestSuite(CompatibilityTestTest.class);
+ addTestSuite(OptionHelperTest.class);
+ addTestSuite(CollectorUtilTest.class);
+ addTestSuite(MetadataReporterTest.class);
+ addTestSuite(ModuleDefTest.class);
+ addTestSuite(ModuleRepoTest.class);
addTestSuite(PropertyCheckTest.class);
addTestSuite(SettingsPreparerTest.class);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
index d5062b3..763acb7 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ChecksumReporterTest.java
@@ -26,6 +26,8 @@
import com.android.compatibility.common.util.TestStatus;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.util.FileUtil;
import junit.framework.TestCase;
@@ -34,6 +36,9 @@
import java.io.FileWriter;
import java.io.IOException;
+/**
+ * Unit tests for {@link ChecksumReporter}
+ */
public class ChecksumReporterTest extends TestCase {
private static final String ROOT_PROPERTY = "TESTS_ROOT";
@@ -81,8 +86,10 @@
setter.setOptionValue("plan", SUITE_PLAN);
setter.setOptionValue("dynamic-config-url", "");
mBuildInfo = provider.getBuild();
+ IInvocationContext context = new InvocationContext();
+ context.addDeviceBuildInfo("fakeDevice", mBuildInfo);
- resultReporter.invocationStarted(mBuildInfo);
+ resultReporter.invocationStarted(context);
mInvocationResult = resultReporter.getResult();
mModuleResult = mInvocationResult.getOrCreateModule("Module-1");
mModuleResult.setDone(true);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
index 272b94d..9102f1a 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
@@ -17,8 +17,8 @@
package com.android.compatibility.common.tradefed.result;
import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.util.AbiUtils;
import junit.framework.TestCase;
@@ -43,7 +43,7 @@
"at four.big.insects.Marley.sing(Marley.java:10)";
private ConsoleReporter mReporter;
- private IBuildInfo mBuildInfo;
+ private IInvocationContext mContext;
@Override
public void setUp() throws Exception {
@@ -58,7 +58,7 @@
}
public void testResultReporting_singleModule() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
mReporter.testRunStarted(ID, 3);
runTests();
@@ -73,7 +73,7 @@
}
public void testResultReporting_multipleModules() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
mReporter.testRunStarted(ID, 3);
runTests();
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
new file mode 100644
index 0000000..17f27a5
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/MetadataReporterTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class MetadataReporterTest extends TestCase {
+
+ private static final String MIN_TEST_DURATION = "2";
+ private static final String ROOT_PROPERTY = "TESTS_ROOT";
+ private static final String BUILD_NUMBER = "2";
+ private static final String SUITE_PLAN = "cts";
+ private static final String DYNAMIC_CONFIG_URL = "";
+ private static final String ROOT_DIR_NAME = "root";
+ private static final String BASE_DIR_NAME = "android-tests";
+ private static final String TESTCASES = "testcases";
+ private static final String NAME = "ModuleName";
+ private static final String ABI = "mips64";
+ private static final String ID = AbiUtils.createId(ABI, NAME);
+ private static final String CLASS = "android.test.FoorBar";
+ private static final String METHOD_1 = "testBlah1";
+ private static final String METHOD_2 = "testBlah2";
+ private static final String METHOD_3 = "testBlah3";
+ private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+ private static final String TEST_2 = String.format("%s#%s", CLASS, METHOD_2);
+ private static final String TEST_3 = String.format("%s#%s", CLASS, METHOD_3);
+ private static final String STACK_TRACE = "Something small is not alright\n " +
+ "at four.big.insects.Marley.sing(Marley.java:10)";
+ private static final String RESULT_DIR = "result123";
+ private static final String[] FORMATTING_FILES = {
+ "compatibility_result.css",
+ "compatibility_result.xsd",
+ "compatibility_result.xsl",
+ "logo.png"};
+ private static final long START_TIME = 123456L;
+
+ private MetadataReporter mReporter;
+ private IBuildInfo mBuildInfo;
+ private IInvocationContext mContext;
+ private CompatibilityBuildHelper mBuildHelper;
+
+ private File mRoot = null;
+ private File mBase = null;
+ private File mTests = null;
+
+ @Override
+ public void setUp() throws Exception {
+
+ mReporter = new MetadataReporter();
+ OptionSetter setter = new OptionSetter(mReporter);
+ setter.setOptionValue("min-test-duration-sec", MIN_TEST_DURATION);
+ mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
+ mBase = new File(mRoot, BASE_DIR_NAME);
+ mBase.mkdirs();
+ mTests = new File(mBase, TESTCASES);
+ mTests.mkdirs();
+ System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
+ mBuildInfo = new BuildInfo(BUILD_NUMBER, "", "");
+ mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+ mContext = new InvocationContext();
+ mContext.addDeviceBuildInfo("fakeDevice", mBuildInfo);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mReporter = null;
+ FileUtil.recursiveDelete(mRoot);
+ }
+
+ public void testResultReportingFastTests() throws Exception {
+ mReporter.invocationStarted(mContext);
+ mReporter.testRunStarted(ID, 3);
+ runFastTests();
+
+ Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+ assertTrue(metadata.isEmpty());
+
+ mReporter.testRunEnded(10, new HashMap<String, String>());
+ mReporter.invocationEnded(10);
+ }
+
+ public void testResultReportingSlowTests() throws Exception {
+ mReporter.invocationStarted(mContext);
+ mReporter.testRunStarted(ID, 3);
+ runSlowTests();
+
+ Collection<MetadataReporter.TestMetadata> metadata = mReporter.getTestMetadata();
+ assertEquals(metadata.size(), 2); // Two passing slow tests.
+
+ mReporter.testRunEnded(10, new HashMap<String, String>());
+ mReporter.invocationEnded(10);
+ }
+
+
+ /** Run 4 test. */
+ private void runFastTests() {
+ TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+ mReporter.testStarted(test1);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+ mReporter.testStarted(test2);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test3);
+ mReporter.testFailed(test3, STACK_TRACE);
+
+ TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test4);
+ mReporter.testIgnored(test4);
+ }
+
+ /** Run 4 tests with delays. 2 passing, 1 failed, 1 ignored. */
+ private void runSlowTests() throws InterruptedException {
+ TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+ mReporter.testStarted(test1);
+ Thread.sleep(3000);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+ mReporter.testStarted(test2);
+ Thread.sleep(3000);
+ mReporter.testEnded(test1, new HashMap<String, String>());
+
+ TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test3);
+ Thread.sleep(3000);
+ mReporter.testFailed(test3, STACK_TRACE);
+
+ TestIdentifier test4 = new TestIdentifier(CLASS, METHOD_3);
+ mReporter.testStarted(test4);
+ Thread.sleep(3000);
+ mReporter.testIgnored(test4);
+ }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index 5af79dc..6fd2401 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -26,6 +26,11 @@
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
@@ -36,6 +41,9 @@
import java.util.HashMap;
import java.util.List;
+/**
+ * Unit tests for {@link ResultReporter}
+ */
public class ResultReporterTest extends TestCase {
private static final String ROOT_PROPERTY = "TESTS_ROOT";
@@ -67,6 +75,7 @@
private ResultReporter mReporter;
private IBuildInfo mBuildInfo;
+ private IInvocationContext mContext;
private CompatibilityBuildHelper mBuildHelper;
private File mRoot = null;
@@ -101,6 +110,8 @@
setter.setOptionValue("dynamic-config-url", DYNAMIC_CONFIG_URL);
mBuildInfo = provider.getBuild();
mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+ mContext = new InvocationContext();
+ mContext.addDeviceBuildInfo("fakeDevice", mBuildInfo);
}
@Override
@@ -110,7 +121,7 @@
}
public void testSetup() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
// Should have created a directory for the logs
File[] children = mBuildHelper.getLogsDir().listFiles();
assertTrue("Didn't create logs dir", children.length == 1 && children[0].isDirectory());
@@ -130,7 +141,7 @@
}
public void testResultReporting() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
mReporter.testRunStarted(ID, 2);
TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
mReporter.testStarted(test1);
@@ -141,6 +152,7 @@
TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
mReporter.testStarted(test3);
mReporter.testFailed(test3, STACK_TRACE);
+ mReporter.testEnded(test3, new HashMap<String, String>());
mReporter.testRunEnded(10, new HashMap<String, String>());
mReporter.invocationEnded(10);
IInvocationResult result = mReporter.getResult();
@@ -188,7 +200,7 @@
public void testRepeatedExecutions() throws Exception {
String[] methods = new String[] {METHOD_1, METHOD_2, METHOD_3};
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
makeTestRun(methods, new boolean[] {true, false, true});
makeTestRun(methods, new boolean[] {true, false, false});
@@ -234,7 +246,7 @@
}
public void testRetry() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
// Set up IInvocationResult with existing results from previous session
mReporter.testRunStarted(ID, 2);
@@ -289,7 +301,7 @@
}
public void testRetryCanSetDone() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
// Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
// perform actual retry)
mReporter.mCanMarkDone = true;
@@ -328,7 +340,7 @@
}
public void testRetryCannotSetDone() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
// Set mCanMarkDone directly (otherwise we must build result directory, write XML, and
// perform actual retry)
mReporter.mCanMarkDone = false;
@@ -367,7 +379,7 @@
}
public void testResultReporting_moduleNotDone() throws Exception {
- mReporter.invocationStarted(mBuildInfo);
+ mReporter.invocationStarted(mContext);
mReporter.testRunStarted(ID, 2);
TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
mReporter.testStarted(test1);
@@ -406,4 +418,27 @@
file.exists() && file.isFile() && file.length() > 0);
}
}
+
+ /**
+ * Ensure that when {@link ResultReporter#testLog(String, LogDataType, InputStreamSource)} is
+ * called, a single invocation result folder is created and populated.
+ */
+ public void testTestLog() throws Exception {
+ InputStreamSource fakeData = new ByteArrayInputStreamSource("test".getBytes());
+ mReporter.invocationStarted(mContext);
+ mReporter.testLog("test1", LogDataType.LOGCAT, fakeData);
+ // date folder
+ assertEquals(1, mBuildHelper.getLogsDir().list().length);
+ // inv_ folder
+ assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
+ // actual logs
+ assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
+ mReporter.testLog("test2", LogDataType.LOGCAT, fakeData);
+ // date folder
+ assertEquals(1, mBuildHelper.getLogsDir().list().length);
+ // inv_ folder
+ assertEquals(1, mBuildHelper.getLogsDir().listFiles()[0].list().length);
+ // actual logs
+ assertEquals(2, mBuildHelper.getLogsDir().listFiles()[0].listFiles()[0].list().length);
+ }
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
index 9c28197..b3bfb91 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
@@ -16,9 +16,11 @@
package com.android.compatibility.common.tradefed.testtype;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.HostTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.FileUtil;
@@ -33,6 +35,10 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
import java.util.List;
/**
@@ -41,10 +47,34 @@
public class JarHostTestTest extends TestCase {
private static final String TEST_JAR1 = "/testtype/testJar1.jar";
+ private static final String TEST_JAR2 = "/testtype/testJar2.jar";
private JarHostTest mTest;
private File mTestDir = null;
/**
+ * More testable version of {@link JarHostTest}
+ */
+ public static class JarHostTestable extends JarHostTest {
+
+ public static File mTestDir;
+ public JarHostTestable() {}
+
+ public JarHostTestable(File testDir) {
+ mTestDir = testDir;
+ }
+
+ @Override
+ CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
+ return new CompatibilityBuildHelper(info) {
+ @Override
+ public File getTestsDir() throws FileNotFoundException {
+ return mTestDir;
+ }
+ };
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -117,17 +147,7 @@
*/
public void testSplit_withJar() throws Exception {
File testJar = getJarResource(TEST_JAR1, mTestDir);
- mTest = new JarHostTest() {
- @Override
- CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
- return new CompatibilityBuildHelper(info) {
- @Override
- public File getTestsDir() throws FileNotFoundException {
- return mTestDir;
- }
- };
- }
- };
+ mTest = new JarHostTestable(mTestDir);
OptionSetter setter = new OptionSetter(mTest);
setter.setOptionValue("jar", testJar.getName());
List<IRemoteTest> res = (List<IRemoteTest>)mTest.split();
@@ -144,31 +164,97 @@
* Test that {@link JarHostTest#getTestShard(int, int)} can split classes coming from a jar.
*/
public void testGetTestShard_withJar() throws Exception {
- File testJar = getJarResource(TEST_JAR1, mTestDir);
- mTest = new JarHostTest() {
- @Override
- CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
- return new CompatibilityBuildHelper(info) {
- @Override
- public File getTestsDir() throws FileNotFoundException {
- return mTestDir;
- }
- };
- }
- };
+ File testJar = getJarResource(TEST_JAR2, mTestDir);
+ mTest = new JarHostTestLoader(mTestDir, testJar);
+ mTest.setBuild(new BuildInfo());
+ ITestDevice device = EasyMock.createNiceMock(ITestDevice.class);
+ mTest.setDevice(device);
OptionSetter setter = new OptionSetter(mTest);
setter.setOptionValue("jar", testJar.getName());
- IRemoteTest shard1 = mTest.getTestShard(3, 0);
+ // full class count without sharding
+ assertEquals(238, mTest.countTestCases());
+
+ // only one shard
+ IRemoteTest oneShard = mTest.getTestShard(1, 0);
+ assertTrue(oneShard instanceof JarHostTest);
+ ((JarHostTest)oneShard).setBuild(new BuildInfo());
+ ((JarHostTest)oneShard).setDevice(device);
+ assertEquals(238, ((JarHostTest)oneShard).countTestCases());
+
+ // 5 shards total the number of tests.
+ int total = 0;
+ IRemoteTest shard1 = mTest.getTestShard(5, 0);
assertTrue(shard1 instanceof JarHostTest);
- assertEquals("[android.ui.cts.TaskSwitchingTest]",
- ((JarHostTest)shard1).getClassNames().toString());
- IRemoteTest shard2 = mTest.getTestShard(3, 1);
+ ((JarHostTest)shard1).setBuild(new BuildInfo());
+ ((JarHostTest)shard1).setDevice(device);
+ assertEquals(78, ((JarHostTest)shard1).countTestCases());
+ total += ((JarHostTest)shard1).countTestCases();
+
+ IRemoteTest shard2 = mTest.getTestShard(5, 1);
assertTrue(shard2 instanceof JarHostTest);
- assertEquals("[android.ui.cts.InstallTimeTest]",
- ((JarHostTest)shard2).getClassNames().toString());
- // Not enough class for a real 3rd shard, so it's an empty placeholder instead.
- IRemoteTest shard3 = mTest.getTestShard(3, 2);
+ ((JarHostTest)shard2).setBuild(new BuildInfo());
+ ((JarHostTest)shard2).setDevice(device);
+ assertEquals(63, ((JarHostTest)shard2).countTestCases());
+ total += ((JarHostTest)shard2).countTestCases();
+
+ IRemoteTest shard3 = mTest.getTestShard(5, 2);
assertTrue(shard3 instanceof JarHostTest);
- assertTrue(((JarHostTest)shard3).getClassNames().isEmpty());
+ ((JarHostTest)shard3).setBuild(new BuildInfo());
+ ((JarHostTest)shard3).setDevice(device);
+ assertEquals(42, ((JarHostTest)shard3).countTestCases());
+ total += ((JarHostTest)shard3).countTestCases();
+
+ IRemoteTest shard4 = mTest.getTestShard(5, 3);
+ assertTrue(shard4 instanceof JarHostTest);
+ ((JarHostTest)shard4).setBuild(new BuildInfo());
+ ((JarHostTest)shard4).setDevice(device);
+ assertEquals(14, ((JarHostTest)shard4).countTestCases());
+ total += ((JarHostTest)shard4).countTestCases();
+
+ IRemoteTest shard5 = mTest.getTestShard(5, 4);
+ assertTrue(shard5 instanceof JarHostTest);
+ ((JarHostTest)shard5).setBuild(new BuildInfo());
+ ((JarHostTest)shard5).setDevice(device);
+ assertEquals(41, ((JarHostTest)shard5).countTestCases());
+ total += ((JarHostTest)shard5).countTestCases();
+
+ assertEquals(238, total);
+ }
+
+ /**
+ * Testable version of {@link JarHostTest} that allows adding jar to classpath for testing
+ * purpose.
+ */
+ public static class JarHostTestLoader extends JarHostTestable {
+
+ private static File mTestJar;
+
+ public JarHostTestLoader() {}
+
+ public JarHostTestLoader(File testDir, File jar) {
+ super(testDir);
+ mTestJar = jar;
+ }
+
+ @Override
+ CompatibilityBuildHelper createBuildHelper(IBuildInfo info) {
+ return new CompatibilityBuildHelper(info) {
+ @Override
+ public File getTestsDir() throws FileNotFoundException {
+ return mTestDir;
+ }
+ };
+ }
+ @Override
+ protected ClassLoader getClassLoader() {
+ ClassLoader child = super.getClassLoader();
+ try {
+ child = new URLClassLoader(Arrays.asList(mTestJar.toURI().toURL())
+ .toArray(new URL[]{}), super.getClassLoader());
+ } catch (MalformedURLException e) {
+ CLog.e(e);
+ }
+ return child;
+ }
}
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
index 02eae24..4aa67ac 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleDefTest.java
@@ -28,9 +28,12 @@
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.util.AbiUtils;
+import org.easymock.EasyMock;
+
import junit.framework.TestCase;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -49,6 +52,21 @@
assertEquals("Incorrect Name", NAME, def.getName());
}
+ public void testModuleFinisher() throws Exception {
+ IAbi abi = new Abi(ABI, "");
+ MockRemoteTest mockTest = new MockRemoteTest();
+ IModuleDef def = new ModuleDef(NAME, abi, mockTest, new ArrayList<ITargetPreparer>());
+ ITestInvocationListener mockListener = EasyMock.createMock(ITestInvocationListener.class);
+ // listener should receive testRunStarted/testRunEnded events even for no-op run() method
+ mockListener.testRunStarted(ID, 0);
+ EasyMock.expectLastCall().once();
+ mockListener.testRunEnded(0, Collections.emptyMap());
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(mockListener);
+ def.run(mockListener);
+ EasyMock.verify(mockListener);
+ }
+
private class MockRemoteTest implements IRemoteTest, ITestFilterReceiver, IAbiReceiver,
IRuntimeHintProvider, ITestCollector {
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
new file mode 100644
index 0000000..199b826
--- /dev/null
+++ b/common/host-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.compatibility.common.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Host-side utility class for reading properties and gathering information for testing
+ * Android device compatibility.
+ */
+public class PropertyUtil {
+
+ /**
+ * Name of read-only property detailing the first API level for which the product was
+ * shipped. Property should be undefined for factory ROM products.
+ */
+ public static String FIRST_API_LEVEL = "ro.product.first_api_level";
+
+ /** Returns whether the device build is the factory ROM */
+ public static boolean isFactoryROM(ITestDevice device) throws DeviceNotAvailableException {
+ // first API level property should be undefined if and only if the product is factory ROM.
+ return device.getProperty(FIRST_API_LEVEL) == null;
+ }
+
+ /**
+ * Return the first API level for this product. If the read-only property is unset,
+ * this means the first API level is the current API level, and the current API level
+ * is returned.
+ */
+ public static int getFirstApiLevel(ITestDevice device) throws DeviceNotAvailableException {
+ String propString = device.getProperty(FIRST_API_LEVEL);
+ return (propString == null) ? device.getApiLevel() : Integer.parseInt(propString);
+ }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 2c63ae4..c251cbe 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -509,6 +509,22 @@
}
/**
+ * Get the result directory for the given sessionId.
+ */
+ public static File getResultDirectory(File resultsDir, Integer sessionId) {
+ if (sessionId < 0) {
+ throw new IllegalArgumentException(
+ String.format("Invalid session id [%d] ", sessionId));
+ }
+ List<File> allResultDirs = getResultDirectories(resultsDir);
+ if (sessionId >= allResultDirs.size()) {
+ throw new IllegalArgumentException(String.format("Invalid session id [%d], results" +
+ "directory contains only %d results", sessionId, allResultDirs.size()));
+ }
+ return allResultDirs.get(sessionId);
+ }
+
+ /**
* Get a list of child directories that contain test invocation results
* @param resultsDir the root test result directory
* @return
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
new file mode 100644
index 0000000..034da99
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantCookieHostTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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 android.appsecurity.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+/**
+ * Tests for the instant cookie APIs
+ */
+public class InstantCookieHostTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String INSTANT_COOKIE_APP_APK = "CtsInstantCookieApp.apk";
+ private static final String INSTANT_COOKIE_APP_PKG = "test.instant.cookie";
+
+ private CompatibilityBuildHelper mBuildHelper;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ uninstallPackage(INSTANT_COOKIE_APP_PKG);
+ clearUserData(INSTANT_COOKIE_APP_PKG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ uninstallPackage(INSTANT_COOKIE_APP_PKG);
+ }
+
+ public void testCookieUpdateAndRetrieval() throws Exception {
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookieUpdateAndRetrieval");
+ }
+
+ public void testCookiePersistedAcrossInstantInstalls() throws Exception {
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookiePersistedAcrossInstantInstalls1");
+ uninstallPackage(INSTANT_COOKIE_APP_PKG);
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookiePersistedAcrossInstantInstalls2");
+ }
+
+ public void testCookiePersistedUpgradeFromInstant() throws Exception {
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, true));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookiePersistedUpgradeFromInstant1");
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookiePersistedUpgradeFromInstant2");
+ }
+
+ public void testCookieResetOnNonInstantReinstall() throws Exception {
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, false, false));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookieResetOnNonInstantReinstall1");
+ uninstallPackage(INSTANT_COOKIE_APP_PKG);
+ assertNull(installPackage(INSTANT_COOKIE_APP_APK, true, false));
+ runDeviceTests(INSTANT_COOKIE_APP_PKG, "test.instant.cookie.CookieTest",
+ "testCookieResetOnNonInstantReinstall2");
+ }
+
+ private String clearUserData(String packageName) throws DeviceNotAvailableException {
+ return getDevice().executeShellCommand("pm clear " + packageName);
+ }
+
+ private String installPackage(String apk, boolean replace, boolean instant) throws Exception {
+ return getDevice().installPackage(mBuildHelper.getTestFile(apk), replace,
+ instant ? "--ephemeral" : "");
+ }
+
+ private String uninstallPackage(String packageName) throws DeviceNotAvailableException {
+ return getDevice().uninstallPackage(packageName);
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+ throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index af53e03..5dae473 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -15,7 +15,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.ephemeralapp1">
+ package="com.android.cts.ephemeralapp1"
+ android:targetSandboxVersion="2">
<application
android:label="@string/app_name">
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
index 8be8549..5475605 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
@@ -15,7 +15,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.ephemeralapp2">
+ package="com.android.cts.ephemeralapp2"
+ android:targetSandboxVersion="2">
<!-- TEST: exists only for testing ephemeral app1 can't see this app -->
<application
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk
new file mode 100644
index 0000000..101d564
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2017 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_PACKAGE_NAME := CtsInstantCookieApp
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml
new file mode 100644
index 0000000..7a937d5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.instant.cookie"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="test.instant.cookie" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java b/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java
new file mode 100644
index 0000000..37f6bba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/src/test/instant/cookie/CookieTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 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 test.instant.cookie;
+
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class CookieTest {
+ @Test
+ public void testCookieUpdateAndRetrieval() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // We should be an instant app
+ assertTrue(pm.isInstantApp());
+
+ // The max cookie size is greater than zero
+ assertTrue(pm.getInstantAppCookieMaxSize() > 0);
+
+ // Initially there is no cookie
+ byte[] cookie = pm.getInstantAppCookie();
+ assertTrue(cookie != null && cookie.length == 0);
+
+ // Setting a cookie below max size should work
+ assertTrue(pm.setInstantAppCookie("1".getBytes()));
+
+ // Setting a cookie above max size should not work
+ assertFalse(pm.setInstantAppCookie(
+ new byte[pm.getInstantAppCookieMaxSize() + 1]));
+
+ // Ensure cookie not modified
+ assertEquals("1", new String(pm.getInstantAppCookie()));
+ }
+
+ @Test
+ public void testCookiePersistedAcrossInstantInstalls1() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // Set a cookie to later check when reinstalled as instant app
+ assertTrue(pm.setInstantAppCookie("2".getBytes()));
+ }
+
+ @Test
+ public void testCookiePersistedAcrossInstantInstalls2() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // After the upgrade the cookie should be the same
+ assertEquals("2", new String(pm.getInstantAppCookie()));
+ }
+
+ @Test
+ public void testCookiePersistedUpgradeFromInstant1() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // Make sure we are an instant app
+ assertTrue(pm.isInstantApp());
+
+ // Set a cookie to later check when upgrade to a normal app
+ assertTrue(pm.setInstantAppCookie("3".getBytes()));
+ }
+
+ @Test
+ public void testCookiePersistedUpgradeFromInstant2() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // Make sure we are not an instant app
+ assertFalse(pm.isInstantApp());
+
+ // The cookie survives the upgrade to a normal app
+ assertEquals("3", new String(pm.getInstantAppCookie()));
+ }
+
+ @Test
+ public void testCookieResetOnNonInstantReinstall1() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // Set a cookie to later check when reinstalled as normal app
+ assertTrue(pm.setInstantAppCookie("4".getBytes()));
+ }
+
+ @Test
+ public void testCookieResetOnNonInstantReinstall2() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ // The cookie should have been wiped if non-instant app is uninstalled
+ byte[] cookie = pm.getInstantAppCookie();
+ assertTrue(cookie != null && cookie.length == 0);
+ }
+}
diff --git a/hostsidetests/bootstats/Android.mk b/hostsidetests/bootstats/Android.mk
new file mode 100644
index 0000000..ca491e0
--- /dev/null
+++ b/hostsidetests/bootstats/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsBootStatsTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_CTS_TEST_PACKAGE := android.bootstats
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
new file mode 100644
index 0000000..b4e6056
--- /dev/null
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for the CTS Boot Stats host tests">
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsBootStatsTestCases.jar" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
new file mode 100644
index 0000000..f7bbd02
--- /dev/null
+++ b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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 android.bootstats.cts;
+
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import junit.framework.Assert;
+
+/**
+ * Set of tests that verify statistics collection during boot.
+ */
+public class BootStatsHostTest extends DeviceTestCase {
+ private static final String TAG = "BootStatsHostTest";
+
+ public void testBootStats() throws Exception {
+ long startTime = System.currentTimeMillis();
+ // Clear buffer to make it easier to find new logs
+ getDevice().executeShellCommand("logcat --buffer=events --clear");
+
+ // reboot device
+ getDevice().rebootUntilOnline();
+ waitForBootCompleted();
+ int upperBoundSeconds = (int) ((System.currentTimeMillis() - startTime) / 1000);
+
+ // wait for logs to post
+ Thread.sleep(10000);
+
+ // find logs and parse them
+ final String log = getDevice().executeShellCommand("logcat --buffer=events -d");
+ int counter = log.indexOf("[boot_complete,");
+ Assert.assertTrue("did not find boot logs", counter != -1);
+ counter += 15;
+ String valueString = log.substring(counter, log.indexOf("]", counter));
+ int bootTime = Integer.valueOf(valueString);
+ Assert.assertTrue("reported boot time must be less than observed boot time",
+ bootTime < upperBoundSeconds);
+ Assert.assertTrue("reported boot time must be non-zero", bootTime > 0);
+ }
+
+ private boolean isBootCompleted() throws Exception {
+ return "1".equals(getDevice().executeShellCommand("getprop sys.boot_completed").trim());
+ }
+
+ private void waitForBootCompleted() throws Exception {
+ for (int i = 0; i < 45; i++) {
+ if (isBootCompleted()) {
+ return;
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("System failed to become ready!");
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
index 0bca687..7a1fa40 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/provisioning/AffiliationTest.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2017 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.cts.comp.provisioning;
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 8683ed5..a2d7a60 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -29,7 +29,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
compatibility-device-util \
- android-support-v4
+ android-support-v4 \
+ android-support-test
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
new file mode 100644
index 0000000..4ccb3c6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.cts.deviceowner;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class AffiliationTest {
+
+ private DevicePolicyManager mDevicePolicyManager;
+ private ComponentName mAdminComponent;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mDevicePolicyManager = (DevicePolicyManager)
+ context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mAdminComponent = BaseDeviceOwnerTest.BasicAdminReceiver.getComponentName(context);
+ }
+
+ @Test
+ public void testSetAffiliationId1() {
+ setAffiliationIds(Collections.singletonList("id.number.1"));
+ }
+
+ @Test
+ public void testSetAffiliationId2() {
+ setAffiliationIds(Collections.singletonList("id.number.2"));
+ }
+
+ @Test
+ public void testLockTaskMethodsThrowExceptionIfUnaffiliated() {
+ checkLockTaskMethodsThrow();
+ }
+
+ /** Assumes that the calling user is already affiliated before calling this method */
+ @Test
+ public void testSetLockTaskPackagesClearedIfUserBecomesUnaffiliated() {
+ final String[] packages = {"package1", "package2"};
+ mDevicePolicyManager.setLockTaskPackages(mAdminComponent, packages);
+ assertArrayEquals(packages, mDevicePolicyManager.getLockTaskPackages(mAdminComponent));
+ assertTrue(mDevicePolicyManager.isLockTaskPermitted("package1"));
+ assertFalse(mDevicePolicyManager.isLockTaskPermitted("package3"));
+
+ final List<String> previousAffiliationIds =
+ mDevicePolicyManager.getAffiliationIds(mAdminComponent);
+ try {
+ // Clearing affiliation ids for this user. Lock task methods unavailable.
+ setAffiliationIds(Collections.<String>emptyList());
+ checkLockTaskMethodsThrow();
+ assertFalse(mDevicePolicyManager.isLockTaskPermitted("package1"));
+
+ // Affiliating the user again. Previously set packages have been cleared.
+ setAffiliationIds(previousAffiliationIds);
+ assertEquals(0, mDevicePolicyManager.getLockTaskPackages(mAdminComponent).length);
+ assertFalse(mDevicePolicyManager.isLockTaskPermitted("package1"));
+ } finally {
+ mDevicePolicyManager.setAffiliationIds(mAdminComponent, previousAffiliationIds);
+ }
+ }
+
+ private void setAffiliationIds(List<String> ids) {
+ mDevicePolicyManager.setAffiliationIds(mAdminComponent, ids);
+ assertEquals(ids, mDevicePolicyManager.getAffiliationIds(mAdminComponent));
+ }
+
+ private void checkLockTaskMethodsThrow() {
+ try {
+ mDevicePolicyManager.setLockTaskPackages(mAdminComponent, new String[0]);
+ fail("setLockTaskPackages did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ try {
+ mDevicePolicyManager.getLockTaskPackages(mAdminComponent);
+ fail("getLockTaskPackages did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
index 15dd07f..69874c8 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
@@ -15,24 +15,38 @@
*/
package com.android.cts.deviceowner;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertArrayEquals;
+
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
-// This is not a standard test of an android activity (such as
-// ActivityInstrumentationTestCase2) as it is attempting to test the actual
-// life cycle and how it is affected by lock task, rather than mock intents
-// and setup.
-public class LockTaskTest extends BaseDeviceOwnerTest {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LockTaskTest {
private static final String TAG = "LockTaskTest";
+ private static final String PACKAGE_NAME = LockTaskTest.class.getPackage().getName();
+ private static final ComponentName ADMIN_COMPONENT =
+ new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
private static final String UTILITY_ACTIVITY
@@ -117,12 +131,17 @@
private final Object mActivityResumedLock = new Object();
private final Object mReceivingActivityCreatedLock = new Object();
+ private Context mContext;
private ActivityManager mActivityManager;
+ private DevicePolicyManager mDevicePolicyManager;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+
+ mDevicePolicyManager = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
IntentFilter filter = new IntentFilter();
filter.addAction(LockTaskUtilityActivity.CREATE_ACTION);
@@ -134,25 +153,29 @@
mContext.registerReceiver(mReceiver, filter);
}
- @Override
- protected void tearDown() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+ @After
+ public void tearDown() {
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
mContext.unregisterReceiver(mReceiver);
- super.tearDown();
}
// Setting and unsetting the lock task packages.
+ @Test
public void testSetLockTaskPackages() {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { TEST_PACKAGE });
+ final String[] packages = new String[] { TEST_PACKAGE, "some.other.package" };
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, packages);
+ assertArrayEquals(packages, mDevicePolicyManager.getLockTaskPackages(ADMIN_COMPONENT));
assertTrue(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
+ assertEquals(0, mDevicePolicyManager.getLockTaskPackages(ADMIN_COMPONENT).length);
assertFalse(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
}
// Start lock task, verify that ActivityManager knows thats what is going on.
+ @Test
public void testStartLockTask() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
waitForResume();
@@ -166,8 +189,9 @@
// Verifies that the act of finishing is blocked by ActivityManager in lock task.
// This results in onDestroy not being called until stopLockTask is called before finish.
+ @Test
public void testCannotFinish() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
// If lock task has not exited then the activity shouldn't actually receive onDestroy.
@@ -179,11 +203,12 @@
}
// Verifies that updating the whitelisting during lock task mode finishes the locked task.
+ @Test
public void testUpdateWhitelisting() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
synchronized (mActivityRunningLock) {
mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
@@ -196,8 +221,9 @@
// This launches an activity that is in the current task.
// This should always be permitted as a part of lock task (since it isn't a new task).
+ @Test
public void testStartActivity_withinTask() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
waitForResume();
@@ -216,8 +242,9 @@
// This launches a whitelisted activity that is not part of the current task.
// This should be permitted as a part of lock task.
+ @Test
public void testStartActivity_outsideTaskWhitelisted() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME,
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
RECEIVING_ACTIVITY_PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
waitForResume();
@@ -235,8 +262,9 @@
// This launches a non-whitelisted activity that is not part of the current task.
// This should be blocked.
+ @Test
public void testStartActivity_outsideTaskNonWhitelisted() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startLockTask(UTILITY_ACTIVITY);
waitForResume();
@@ -251,8 +279,9 @@
// Test the lockTaskMode flag for an activity declaring if_whitelisted.
// Whitelist the activity and verify that lock task mode is started.
+ @Test
public void testManifestArgument_whitelisted() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
waitForResume();
@@ -265,6 +294,7 @@
// Test the lockTaskMode flag for an activity declaring if_whitelisted.
// Don't whitelist the activity and verify that lock task mode is not started.
+ @Test
public void testManifestArgument_nonWhitelisted() throws Exception {
startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
waitForResume();
@@ -278,8 +308,9 @@
// Test the lockTaskMode flag for an activity declaring if_whitelisted.
// An activity locked via manifest argument cannot finish without calling stopLockTask.
+ @Test
public void testManifestArgument_cannotFinish() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
waitForResume();
@@ -293,12 +324,13 @@
// Test the lockTaskMode flag for an activity declaring if_whitelisted.
// Verifies that updating the whitelisting during lock task mode finishes the locked task.
+ @Test
public void testManifestArgument_updateWhitelisting() throws Exception {
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
startAndWait(getLockTaskUtility(UTILITY_ACTIVITY_IF_WHITELISTED));
waitForResume();
- mDevicePolicyManager.setLockTaskPackages(getWho(), new String[0]);
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
synchronized (mActivityRunningLock) {
mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index d7eaf92..994dc62 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -47,6 +47,11 @@
</activity>
<service android:name=".SimpleService" android:exported="true">
</service>
+ <receiver android:name=".SimpleReceiver" android:exported="true">
+ </receiver>
+ <receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
+ android:exported="true">
+ </receiver>
</application>
</manifest>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java
new file mode 100644
index 0000000..b82d04d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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.cts.launcherapps.simpleapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SimpleReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ //Log.i("xxx", "SimpleReceiver: " + intent);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java
new file mode 100644
index 0000000..de767d8
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleRemoteReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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.cts.launcherapps.simpleapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class SimpleRemoteReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ //Log.i("xxx", "SimpleRemoteReceiver: " + intent);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java b/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
index 7a5dee3..edd9de6 100644
--- a/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
+++ b/hostsidetests/devicepolicy/app/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
@@ -18,6 +18,8 @@
import android.R;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -36,6 +38,7 @@
private static String TAG = "ReflectorVpnService";
private static final int NOTIFICATION_ID = 1;
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
private static int MTU = 1799;
private ParcelFileDescriptor mFd = null;
@@ -50,7 +53,11 @@
public int onStartCommand(Intent intent, int flags, int startId) {
// Put ourself in the foreground to stop the system killing us while we wait for orders from
// the hostside test.
- startForeground(NOTIFICATION_ID, new Notification.Builder(this)
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_dialog_alert)
.build());
start();
@@ -60,6 +67,8 @@
@Override
public void onDestroy() {
stop();
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
super.onDestroy();
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index bd32885..bf5049c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -323,7 +323,7 @@
/** Reboots the device and block until the boot complete flag is set. */
protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
getDevice().executeShellCommand("reboot");
- assertTrue("Device failed to boot", getDevice().waitForBootComplete(60000));
+ assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
}
/** Returns true if the system supports the split between system and primary user. */
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 94e528b..43ca4e0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -255,6 +255,14 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
}
+ /**
+ * Require a device for tests that use the network stack. Headless Androids running in
+ * data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
+ * connection alive.
+ *
+ * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
+ * network rules for this user will affect UID 0.
+ */
@RequiresDevice
public void testAlwaysOnVpn() throws Exception {
if (!mHasFeature) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 676f455..80aa652 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,9 +16,6 @@
package com.android.cts.devicepolicy;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -343,7 +340,7 @@
executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval");
}
- public void testLockTask() throws Exception {
+ public void testLockTask_deviceOwnerUser() throws Exception {
if (!mHasFeature) {
return;
}
@@ -361,6 +358,52 @@
}
}
+ public void testLockTask_unaffiliatedUser() throws Exception {
+ if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+ return;
+ }
+
+ final int userId = createUser();
+ installAppAsUser(DEVICE_OWNER_APK, userId);
+ setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG,
+ ".AffiliationTest",
+ "testLockTaskMethodsThrowExceptionIfUnaffiliated",
+ userId);
+
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG,
+ ".AffiliationTest",
+ "testSetLockTaskPackagesClearedIfUserBecomesUnaffiliated",
+ userId);
+ }
+
+ public void testLockTask_affiliatedSecondaryUser() throws Exception {
+ if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+ return;
+ }
+
+ final int userId = createUser();
+ installAppAsUser(INTENT_RECEIVER_APK, userId);
+ installAppAsUser(DEVICE_OWNER_APK, userId);
+ setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+ switchUser(userId);
+
+ // Setting the same affiliation ids on both users and running the lock task tests.
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".LockTaskTest", userId);
+ }
+
public void testSystemUpdatePolicy() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 212671a..01e55fe 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -57,29 +57,5 @@
super.tearDown();
}
- /**
- * Require a device for tests that use the network stack. Headless Android setups running in
- * data centres may need their network rules un-tampered-with in order to keep the ADB / VNC
- * connection alive.
- *
- * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
- * network rules for this user will affect UID 0.
- */
-
- @Override @RequiresDevice
- public void testAlwaysOnVpn() throws Exception {
- super.testAlwaysOnVpn();
- }
-
- @Override @RequiresDevice
- public void testAlwaysOnVpnLockDown() throws Exception {
- super.testAlwaysOnVpnLockDown();
- }
-
- @Override @RequiresDevice
- public void testAlwaysOnVpnPackageUninstalled() throws Exception {
- super.testAlwaysOnVpnPackageUninstalled();
- }
-
- // All other tests for this class are defined in DeviceAndProfileOwnerTest
+ // All tests for this class are defined in DeviceAndProfileOwnerTest
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 81b1b54..ccc95be 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -131,4 +131,23 @@
// DISALLOW_UNMUTE_MICROPHONE and DISALLOW_ADJUST_VOLUME can only be set by device owners
// and profile owners on the primary user.
}
+
+ /**
+ * Don't require a device for tests that use the network stack on secondary users.
+ */
+ @Override
+ public void testAlwaysOnVpn() throws Exception {
+ super.testAlwaysOnVpn();
+ }
+
+ @Override
+ public void testAlwaysOnVpnLockDown() throws Exception {
+ super.testAlwaysOnVpnLockDown();
+ }
+
+ @Override
+ public void testAlwaysOnVpnPackageUninstalled() throws Exception {
+ super.testAlwaysOnVpnPackageUninstalled();
+ }
+
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index d7267b6..803cf36 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -50,28 +50,4 @@
}
super.tearDown();
}
-
- /**
- * Require a device for tests that use the network stack. Headless Android setups running in
- * data centres may need their network rules un-tampered-with in order to keep the ADB / VNC
- * connection alive.
- *
- * This is only a problem on device owner / profile owner running on USER_SYSTEM, because
- * network rules for this user will affect UID 0.
- */
-
- @Override @RequiresDevice
- public void testAlwaysOnVpn() throws Exception {
- super.testAlwaysOnVpn();
- }
-
- @Override @RequiresDevice
- public void testAlwaysOnVpnLockDown() throws Exception {
- super.testAlwaysOnVpnLockDown();
- }
-
- @Override @RequiresDevice
- public void testAlwaysOnVpnPackageUninstalled() throws Exception {
- super.testAlwaysOnVpnPackageUninstalled();
- }
}
diff --git a/hostsidetests/dumpsys/apps/Android.mk b/hostsidetests/dumpsys/apps/Android.mk
new file mode 100644
index 0000000..4a74e80
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/Android.mk b/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.mk
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/Android.mk
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/Android.mk
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/AndroidManifest.xml
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
diff --git a/hostsidetests/dumpsys/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java b/hostsidetests/dumpsys/apps/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
similarity index 100%
rename from hostsidetests/dumpsys/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
rename to hostsidetests/dumpsys/apps/FramestatsTestApp/src/com/android/cts/framestatstestapp/FramestatsTestAppActivity.java
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
new file mode 100644
index 0000000..dc9e4c9
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsProcStatsHelperApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..3944763
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.cts.procstatshelper"
+ android:versionCode="32123">
+
+ <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24"/>
+
+ <application>
+ <activity android:name=".MainActivity"
+ android:exported="true" />
+ <activity android:name=".FlashingActivity"
+ android:exported="true" />
+ <service android:name=".ProcStatsHelperServiceMain"
+ android:exported="true" />
+ <service android:name=".ProcStatsHelperServiceSub" android:process=":proc2"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java
new file mode 100644
index 0000000..7dcce60
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/FlashingActivity.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.cts.procstatshelper;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class FlashingActivity extends Activity {
+ private static final String TAG = "FlashingActivity";
+
+ private static Semaphore mGate = new Semaphore(1);
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG, "onResume");
+ super.onResume();
+
+ new Handler().postDelayed(() -> finish(), 100);
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ super.onDestroy();
+
+ mGate.release();
+ }
+
+ public static void flash(Context context) {
+ Log.i(TAG, "flash");
+ try {
+ mGate.acquire();
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(context, FlashingActivity.class))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ context.startActivity(intent);
+
+ if (!mGate.tryAcquire(10, TimeUnit.SECONDS)) {
+ throw new RuntimeException("Activity didn't start.");
+ }
+ mGate.release();
+
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException", e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
similarity index 67%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
index 4e3fc89..40e657c 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/MainActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -11,18 +11,19 @@
* 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
+ * limitations under the License.
*/
-
-package android.server.cts;
+package com.android.server.cts.procstatshelper;
import android.app.Activity;
-import android.graphics.Rect;
+import android.os.Handler;
-public class LaunchTapToFinishPipActivity extends Activity {
+public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
- PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+
+ // Close in 1 second.
+ new Handler().postDelayed(() -> finish(), 1000);
}
}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java
new file mode 100644
index 0000000..2ee5628
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceBase.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 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.cts.procstatshelper;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.content.Intent;
+import android.util.Log;
+
+public class ProcStatsHelperServiceBase extends IntentService {
+ private static final String TAG = "ProcStatsHelperService";
+
+ public ProcStatsHelperServiceBase() {
+ super(TAG);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.e(TAG, "onCreate: " + getClass().getName());
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.e(TAG, "onHandleIntent: " + getClass().getName());
+
+ // Run as a background service for 500 ms.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Caught exception", e);
+ }
+
+ Notification notification = new Notification.Builder(getApplicationContext())
+ .setContentTitle("FgService")
+ .setSmallIcon(android.R.drawable.ic_popup_sync)
+ .build();
+ startForeground(1, notification);
+ // Run as a foreground service for 1 second.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Caught exception", e);
+ }
+ }
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java
new file mode 100644
index 0000000..5fffc21
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceMain.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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.cts.procstatshelper;
+
+public class ProcStatsHelperServiceMain extends ProcStatsHelperServiceBase {
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java
new file mode 100644
index 0000000..63e94b9
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/src/com/android/server/cts/procstatshelper/ProcStatsHelperServiceSub.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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.cts.procstatshelper;
+
+public class ProcStatsHelperServiceSub extends ProcStatsHelperServiceBase {
+}
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
new file mode 100644
index 0000000..1691103
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsProcStatsApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..56cacb5
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.cts.procstats" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.cts.procstats" />
+</manifest>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java b/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java
new file mode 100644
index 0000000..c27eb1e
--- /dev/null
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/src/com/android/server/cts/procstats/ProcStatsTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.cts.procstats;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.fail;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Used by NetstatsIncidentTest. Makes some network requests so "dumpsys netstats" will have
+ * something to show.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ProcStatsTest {
+ private static final String TAG = "ProcStatsTest";
+
+ @After
+ public void tearDown() {
+ runCommand("dumpsys procstats --stop-pretend-screen", "^$");
+ }
+
+ @Test
+ public void testLaunchApp() throws Exception {
+
+ InstrumentationRegistry.getContext().startActivity(new Intent()
+ .setComponent(ComponentName.unflattenFromString(
+ "com.android.server.cts.procstatshelper/.MainActivity")));
+
+ Thread.sleep(3000);
+
+ InstrumentationRegistry.getContext().startService(new Intent()
+ .setComponent(ComponentName.unflattenFromString(
+ "com.android.server.cts.procstatshelper/.ProcStatsHelperServiceMain")));
+
+ Thread.sleep(3000);
+
+ InstrumentationRegistry.getContext().startService(new Intent()
+ .setComponent(ComponentName.unflattenFromString(
+ "com.android.server.cts.procstatshelper/.ProcStatsHelperServiceSub")));
+
+ Thread.sleep(3000);
+
+ // Now run something with the screen off.
+ runCommand("dumpsys procstats --pretend-screen-off", "^$");
+
+ InstrumentationRegistry.getContext().startActivity(new Intent()
+ .setComponent(ComponentName.unflattenFromString(
+ "com.android.server.cts.procstatshelper/.MainActivity")));
+
+ Thread.sleep(3000);
+
+ // run "dumpsys meminfo" to update the PSS stats.
+ runCommand("dumpsys meminfo com.android.server.cts.procstatshelper",
+ "MEMINFO in pid");
+ runCommand("dumpsys meminfo com.android.server.cts.procstatshelper:proc2",
+ "MEMINFO in pid");
+ }
+
+ static List<String> readAll(ParcelFileDescriptor pfd) {
+ try {
+ try {
+ final ArrayList<String> ret = new ArrayList<>();
+ try (BufferedReader r = new BufferedReader(
+ new FileReader(pfd.getFileDescriptor()))) {
+ String line;
+ while ((line = r.readLine()) != null) {
+ ret.add(line);
+ }
+ r.readLine();
+ }
+ return ret;
+ } finally {
+ pfd.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static String concatResult(List<String> result) {
+ final StringBuilder sb = new StringBuilder();
+ for (String s : result) {
+ sb.append(s);
+ sb.append("\n");
+ }
+ return sb.toString().trim();
+ }
+
+ private void runCommand(String command, String expectedOutputRegex) {
+ Log.i(TAG, "Running comamnd: " + command);
+ final String result = concatResult(readAll(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ command)));
+ Log.i(TAG, "Output:");
+ Log.i(TAG, result);
+ if (!Pattern.compile(expectedOutputRegex).matcher(result).find()) {
+ fail("Expected=" + expectedOutputRegex + "\nBut was=" + result);
+ }
+ }
+}
+
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
index aecb401..9258d433 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
@@ -16,16 +16,35 @@
package android.dumpsys.cts;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import java.io.FileNotFoundException;
+import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
public class BaseDumpsysTest extends DeviceTestCase implements IBuildReceiver {
protected static final String TAG = "DumpsysHostTest";
+ private static final String TEST_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
/**
* A reference to the device under test.
*/
@@ -54,6 +73,47 @@
}
}
+ protected static long assertNonNegativeInteger(String input) {
+ try {
+ final long result = Long.parseLong(input);
+ assertTrue("Expected non-negative, but was: " + result, result >= 0);
+
+ return result;
+ } catch (NumberFormatException e) {
+ fail("Expected an integer but found \"" + input + "\"");
+ // Won't be hit, above throws AssertException
+ return -1;
+ }
+ }
+
+ protected static long assertPositiveInteger(String input) {
+ try {
+ final long result = Long.parseLong(input);
+ assertTrue("Expected positive, but was: " + result, result > 0);
+
+ return result;
+ } catch (NumberFormatException e) {
+ fail("Expected an integer but found \"" + input + "\"");
+ // Won't be hit, above throws AssertException
+ return -1;
+ }
+ }
+
+ protected static void assertMinAvgMax(String min, String avg, String max, boolean checkAvg) {
+ final long lMin = assertNonNegativeInteger(min);
+ final long lAvg = assertNonNegativeInteger(avg);
+ final long lMax = assertNonNegativeInteger(max);
+
+ if (checkAvg) {
+ assertTrue("min [" + min + "] <= avg [" + avg + "]", lMin <= lAvg);
+ assertTrue("avg [" + avg + "] <= max [" + max + "]", lAvg <= lMax);
+ } else {
+ // There was a bug in the average calculation, so we can't check the average
+ // from the last N hour stats, which may be generated on with the buggy logic.
+ assertTrue("min [" + min + "] <= max [" + max + "]", lMin <= lMax);
+ }
+ }
+
protected static double assertDouble(String input) {
try {
return Double.parseDouble(input);
@@ -66,4 +126,93 @@
protected static void assertSeenTag(Set<String> seenTags, String tag) {
assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
}
+
+
+ /**
+ * Install a device side test package.
+ *
+ * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+ * @param grantPermissions whether to give runtime permissions.
+ */
+ protected void installPackage(String appFileName, boolean grantPermissions)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ final String result = getDevice().installPackage(
+ buildHelper.getTestFile(appFileName), true, grantPermissions);
+ assertNull("Failed to install " + appFileName + ": " + result, result);
+ }
+
+ /**
+ * Run a device side test.
+ *
+ * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+ * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+ * @param testMethodName Test method name.
+ * @throws DeviceNotAvailableException
+ */
+ protected void runDeviceTests(@Nonnull String pkgName,
+ @Nullable String testClassName, @Nullable String testMethodName)
+ throws DeviceNotAvailableException {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ pkgName, TEST_RUNNER, getDevice().getIDevice());
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+
+ final TestRunResult result = listener.getCurrentRunResults();
+ if (result.isRunFailure()) {
+ throw new AssertionError("Failed to successfully run device tests for "
+ + result.getName() + ": " + result.getRunFailureMessage());
+ }
+ if (result.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+
+ if (result.hasFailedTests()) {
+ // build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+ for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+ result.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+ }
+
+ /**
+ * Execute the given command, and find the given pattern and return the resulting
+ * {@link Matcher}.
+ */
+ protected Matcher execCommandAndFind(String command, String pattern) throws Exception {
+ final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ getDevice().executeShellCommand(command, receiver);
+ final String output = receiver.getOutput();
+ final Matcher matcher = Pattern.compile(pattern).matcher(output);
+ assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
+ return matcher;
+ }
+
+ /**
+ * Execute the given command, find the given pattern, and return the first captured group
+ * as a String.
+ */
+ protected String execCommandAndGetFirstGroup(String command, String pattern) throws Exception {
+ final Matcher matcher = execCommandAndFind(command, pattern);
+ assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
+ return matcher.group(1);
+ }
}
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 9c17248..f69ceab 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -237,10 +237,11 @@
}
private void checkSensor(String[] parts) {
- assertEquals(7, parts.length);
+ assertEquals(8, parts.length);
assertInteger(parts[4]); // sensorNumber
assertInteger(parts[5]); // totalTime
assertInteger(parts[6]); // count
+ assertInteger(parts[7]); // backgroundCount
}
private void checkVibrator(String[] parts) {
@@ -256,24 +257,36 @@
}
private void checkStateTime(String[] parts) {
- assertEquals(7, parts.length);
- assertInteger(parts[4]); // foreground
- assertInteger(parts[5]); // active
- assertInteger(parts[6]); // running
+ assertEquals(10, parts.length);
+ assertInteger(parts[4]); // top
+ assertInteger(parts[5]); // foreground_service
+ assertInteger(parts[6]); // top_sleeping
+ assertInteger(parts[7]); // foreground
+ assertInteger(parts[8]); // background
+ assertInteger(parts[9]); // cached
}
private void checkWakelock(String[] parts) {
- assertEquals(14, parts.length);
+ assertEquals(20, parts.length);
assertNotNull(parts[4]); // wakelock
+
assertInteger(parts[5]); // full totalTime
assertEquals("f", parts[6]); // full
long full_count = assertInteger(parts[7]); // full count
- assertInteger(parts[8]); // partial totalTime
- assertEquals("p", parts[9]); // partial
- long partial_count = assertInteger(parts[10]); // partial count
- assertInteger(parts[11]); // window totalTime
- assertEquals("w", parts[12]); // window
- long window_count = assertInteger(parts[13]); // window count
+ assertInteger(parts[8]); // current
+ assertInteger(parts[9]); // max
+
+ assertInteger(parts[10]); // partial totalTime
+ assertEquals("p", parts[11]); // partial
+ long partial_count = assertInteger(parts[12]); // partial count
+ assertInteger(parts[13]); // current
+ assertInteger(parts[14]); // max
+
+ assertInteger(parts[15]); // window totalTime
+ assertEquals("w", parts[16]); // window
+ long window_count = assertInteger(parts[17]); // window count
+ assertInteger(parts[18]); // current
+ assertInteger(parts[19]); // max
// Sanity checks.
assertTrue("full wakelock count must be >= 0", full_count >= 0);
@@ -312,7 +325,7 @@
}
private void checkNetwork(String[] parts) {
- assertEquals(14, parts.length);
+ assertEquals(18, parts.length);
long mbRx = assertInteger(parts[4]); // mobileBytesRx
long mbTx = assertInteger(parts[5]); // mobileBytesTx
long wbRx = assertInteger(parts[6]); // wifiBytesRx
@@ -323,6 +336,10 @@
long wpTx = assertInteger(parts[11]); // wifiPacketsTx
assertInteger(parts[12]); // mobileActiveTime (usec)
assertInteger(parts[13]); // mobileActiveCount
+ assertInteger(parts[14]); // btBytesRx
+ assertInteger(parts[15]); // btBytesTx
+ assertInteger(parts[16]); // mobileWakeup
+ assertInteger(parts[17]); // wifiWakeup
// Assuming each packet contains some bytes, bytes >= packets >= 0.
assertTrue("mobileBytesRx must be >= mobilePacketsRx", mbRx >= mpRx);
@@ -336,14 +353,15 @@
}
private void checkUserActivity(String[] parts) {
- assertEquals(7, parts.length);
+ assertEquals(8, parts.length);
assertInteger(parts[4]); // other
assertInteger(parts[5]); // button
assertInteger(parts[6]); // touch
+ assertInteger(parts[7]); // accessibility
}
private void checkBattery(String[] parts) {
- assertEquals(12, parts.length);
+ assertEquals(13, parts.length);
if (!parts[4].equals("N/A")) {
assertInteger(parts[4]); // startCount
}
@@ -354,7 +372,7 @@
assertInteger(parts[9]); // startClockTime
long bOffReal = assertInteger(parts[10]); // batteryScreenOffRealtime
long bOffUp = assertInteger(parts[11]); // batteryScreenOffUptime
-
+ long bEstCap = assertInteger(parts[12]); // batteryEstimatedCapacity
// The device cannot be up more than there are real-world seconds.
assertTrue("batteryRealtime must be >= batteryUptime", bReal >= bUp);
assertTrue("totalRealtime must be >= totalUptime", tReal >= tUp);
@@ -368,14 +386,17 @@
assertTrue("totalUptime must be >= batteryUptime", tUp >= bUp);
assertTrue("batteryUptime must be >= batteryScreenOffUptime", bUp >= bOffUp);
assertTrue("batteryScreenOffUptime must be >= 0", bOffUp >= 0);
+ assertTrue("batteryEstimatedCapacity must be >= 0", bEstCap >= 0);
}
private void checkBatteryDischarge(String[] parts) {
- assertEquals(8, parts.length);
+ assertEquals(10, parts.length);
assertInteger(parts[4]); // low
assertInteger(parts[5]); // high
assertInteger(parts[6]); // screenOn
assertInteger(parts[7]); // screenOff
+ assertInteger(parts[8]); // dischargeCount
+ assertInteger(parts[9]); // dischargeScreenOffCount
}
private void checkBatteryLevel(String[] parts) {
@@ -411,7 +432,7 @@
}
private void checkGlobalNetwork(String[] parts) {
- assertEquals(12, parts.length);
+ assertEquals(14, parts.length);
assertInteger(parts[4]); // mobileRxTotalBytes
assertInteger(parts[5]); // mobileTxTotalBytes
assertInteger(parts[6]); // wifiRxTotalBytes
@@ -420,6 +441,8 @@
assertInteger(parts[9]); // mobileTxTotalPackets
assertInteger(parts[10]); // wifiRxTotalPackets
assertInteger(parts[11]); // wifiTxTotalPackets
+ assertInteger(parts[12]); // btRxTotalBytes
+ assertInteger(parts[13]); // btTxTotalBytes
}
private void checkScreenBrightness(String[] parts) {
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
index d78bf93..1e67d6b 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/ProcessStatsDumpsysTest.java
@@ -16,6 +16,8 @@
package android.dumpsys.cts;
+import com.android.tradefed.log.LogUtil.CLog;
+
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.HashSet;
@@ -25,28 +27,54 @@
* Test to check the format of the dumps of the processstats test.
*/
public class ProcessStatsDumpsysTest extends BaseDumpsysTest {
+ private static final String DEVICE_SIDE_TEST_APK = "CtsProcStatsApp.apk";
+ private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.procstats";
+
+ private static final String DEVICE_SIDE_HELPER_APK = "CtsProcStatsHelperApp.apk";
+ private static final String DEVICE_SIDE_HELPER_PACKAGE = "com.android.server.cts.procstatshelper";
+
+ private static final boolean UNINSTALL_APPS_AFTER_TESTS = true; // DON'T SUBMIT WITH TRUE
+
/**
* Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
* --checkin", since the latter is not idempotent.
- *
- * @throws Exception
*/
public void testProcstatsOutput() throws Exception {
+ // First, run the helper app so that we have some interesting records in the output.
+ checkWithProcStatsApp();
+
String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
assertNotNull(procstats);
assertTrue(procstats.length() > 0);
- Set<String> seenTags = new HashSet<>();
- int version = -1;
+ final int sep24h = procstats.indexOf("AGGREGATED OVER LAST 24 HOURS:");
+ final int sep3h = procstats.indexOf("AGGREGATED OVER LAST 3 HOURS:");
+
+ assertTrue("24 hour stats not found.", sep24h > 1);
+ assertTrue("3 hour stats not found.", sep3h > 1);
+
+ // Current
+ checkProcStateOutput(procstats.substring(0, sep24h), /*checkAvg=*/ true);
+
+ // Last 24 hours
+ checkProcStateOutput(procstats.substring(sep24h, sep3h), /*checkAvg=*/ false);
+
+ // Last 3 hours
+ checkProcStateOutput(procstats.substring(sep3h), /*checkAvg=*/ false);
+ }
+
+ private void checkProcStateOutput(String text, boolean checkAvg) throws Exception {
+ final Set<String> seenTags = new HashSet<>();
try (BufferedReader reader = new BufferedReader(
- new StringReader(procstats))) {
+ new StringReader(text))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.isEmpty()) {
continue;
}
+ CLog.d("Checking line: " + line);
// extra space to make sure last column shows up.
if (line.endsWith(",")) {
@@ -58,34 +86,34 @@
switch (parts[0]) {
case "vers":
assertEquals(2, parts.length);
- version = Integer.parseInt(parts[1]);
+ assertEquals(5, Integer.parseInt(parts[1]));
break;
case "period":
checkPeriod(parts);
break;
case "pkgproc":
- checkPkgProc(parts, version);
+ checkPkgProc(parts);
break;
case "pkgpss":
- checkPkgPss(parts, version);
+ checkPkgPss(parts, checkAvg);
break;
case "pkgsvc-bound":
case "pkgsvc-exec":
case "pkgsvc-run":
case "pkgsvc-start":
- checkPkgSvc(parts, version);
+ checkPkgSvc(parts);
break;
case "pkgkills":
- checkPkgKills(parts, version);
+ checkPkgKills(parts, checkAvg);
break;
case "proc":
checkProc(parts);
break;
case "pss":
- checkPss(parts);
+ checkPss(parts, checkAvg);
break;
case "kills":
- checkKills(parts);
+ checkKills(parts, checkAvg);
break;
case "total":
checkTotal(parts);
@@ -96,60 +124,45 @@
}
}
-/* more kinds
-total 0n:4426040 0m:5779 1n:6067104 1m:74489
-sysmemusage 0np1791668:791668:791668:40080:40080:40080:1176:1176:1176:147068:147068:147068:230682:230682:230682 ...
-weights 10573412 6.133572860728E12:40 ...
-availablepages Unmovable 0 69 37 31 14 20 28 16 5 7 0 0
-availablepages Reclaimable 0 0 0 0 1 1 1 1 0 0 1 0
-availablepages Movable 0 1 0 1 1 1 2 0 1 1 0 173
-availablepages Reserve 0 0 0 0 0 0 0 0 0 0 0 2
-availablepages CMA 0 1 1 98 111 70 35 20 7 9 3 18
-availablepages Isolate
- */
-
- // spot check a few tags
+ assertSeenTag(seenTags, "vers");
+ assertSeenTag(seenTags, "period");
assertSeenTag(seenTags, "pkgproc");
assertSeenTag(seenTags, "proc");
assertSeenTag(seenTags, "pss");
assertSeenTag(seenTags, "total");
+ assertSeenTag(seenTags, "weights");
+ assertSeenTag(seenTags, "availablepages");
}
private void checkPeriod(String[] parts) {
assertTrue("Expected 5 or 6, found: " + parts.length,
parts.length == 5 || parts.length == 6);
assertNotNull(parts[1]); // date
- assertInteger(parts[2]); // start time (msec)
- assertInteger(parts[3]); // end time (msec)
+ assertNonNegativeInteger(parts[2]); // start time (msec)
+ assertNonNegativeInteger(parts[3]); // end time (msec)
+
+ // TODO Check the values.
assertNotNull(parts[4]); // status
if (parts.length == 6) {
assertNotNull(parts[5]); // swapped-out-pss
}
}
- private void checkPkgProc(String[] parts, int version) {
+ private void checkPkgProc(String[] parts) {
int statesStartIndex;
- if (version < 4) {
- assertTrue(parts.length >= 4);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- statesStartIndex = 4;
- } else {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- statesStartIndex = 5;
- }
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertNonNegativeInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
for (int i = statesStartIndex; i < parts.length; i++) {
String[] subparts = parts[i].split(":");
assertEquals(2, subparts.length);
checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // duration (msec)
+ assertNonNegativeInteger(subparts[1]); // duration (msec)
}
}
@@ -170,147 +183,107 @@
if (hasProcess) {
char p = tag.charAt(2);
- assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+ assertTrue("malformed tag: " + tag, "ptfbuwsxrhlace".indexOf(p) >= 0);
}
}
- private void checkPkgPss(String[] parts, int version) {
+ private void checkPkgPss(String[] parts, boolean checkAvg) {
int statesStartIndex;
- if (version < 4) {
- assertTrue(parts.length >= 4);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- statesStartIndex = 4;
- } else {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- statesStartIndex = 5;
- }
+ assertTrue(parts.length >= 5);
+ assertNotNull(parts[1]); // package name
+ assertNonNegativeInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ statesStartIndex = 5;
for (int i = statesStartIndex; i < parts.length; i++) {
String[] subparts = parts[i].split(":");
assertEquals(8, subparts.length);
checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // sample size
- assertInteger(subparts[2]); // pss min
- assertInteger(subparts[3]); // pss avg
- assertInteger(subparts[4]); // pss max
- assertInteger(subparts[5]); // uss min
- assertInteger(subparts[6]); // uss avg
- assertInteger(subparts[7]); // uss max
+ assertNonNegativeInteger(subparts[1]); // sample size
+ assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss
+ assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss
}
}
- private void checkPkgSvc(String[] parts, int version) {
+ private void checkPkgSvc(String[] parts) {
int statesStartIndex;
- if (version < 4) {
- assertTrue(parts.length >= 5);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // service name
- assertInteger(parts[4]); // count
- statesStartIndex = 5;
- } else {
- assertTrue(parts.length >= 6);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // service name
- assertInteger(parts[5]); // count
- statesStartIndex = 6;
- }
+ assertTrue(parts.length >= 6);
+ assertNotNull(parts[1]); // package name
+ assertNonNegativeInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // service name
+ assertNonNegativeInteger(parts[5]); // count
+ statesStartIndex = 6;
for (int i = statesStartIndex; i < parts.length; i++) {
String[] subparts = parts[i].split(":");
assertEquals(2, subparts.length);
checkTag(subparts[0], false); // tag
- assertInteger(subparts[1]); // duration (msec)
+ assertNonNegativeInteger(subparts[1]); // duration (msec)
}
}
- private void checkPkgKills(String[] parts, int version) {
+ private void checkPkgKills(String[] parts, boolean checkAvg) {
String pssStr;
- if (version < 4) {
- assertEquals(8, parts.length);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertNotNull(parts[3]); // process
- assertInteger(parts[4]); // wakes
- assertInteger(parts[5]); // cpu
- assertInteger(parts[6]); // cached
- pssStr = parts[7];
- } else {
- assertEquals(9, parts.length);
- assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // app version
- assertNotNull(parts[4]); // process
- assertInteger(parts[5]); // wakes
- assertInteger(parts[6]); // cpu
- assertInteger(parts[7]); // cached
- pssStr = parts[8];
- }
+ assertEquals(9, parts.length);
+ assertNotNull(parts[1]); // package name
+ assertNonNegativeInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[3]); // app version
+ assertNotNull(parts[4]); // process
+ assertNonNegativeInteger(parts[5]); // wakes
+ assertNonNegativeInteger(parts[6]); // cpu
+ assertNonNegativeInteger(parts[7]); // cached
+ pssStr = parts[8];
String[] subparts = pssStr.split(":");
assertEquals(3, subparts.length);
- assertInteger(subparts[0]); // pss min
- assertInteger(subparts[1]); // pss avg
- assertInteger(subparts[2]); // pss max
+ assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss
}
private void checkProc(String[] parts) {
assertTrue(parts.length >= 3);
assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[2]); // uid
for (int i = 3; i < parts.length; i++) {
String[] subparts = parts[i].split(":");
assertEquals(2, subparts.length);
checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // duration (msec)
+ assertNonNegativeInteger(subparts[1]); // duration (msec)
}
}
- private void checkPss(String[] parts) {
+ private void checkPss(String[] parts, boolean checkAvg) {
assertTrue(parts.length >= 3);
assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[2]); // uid
for (int i = 3; i < parts.length; i++) {
String[] subparts = parts[i].split(":");
assertEquals(8, subparts.length);
checkTag(subparts[0], true); // tag
- assertInteger(subparts[1]); // sample size
- assertInteger(subparts[2]); // pss min
- assertInteger(subparts[3]); // pss avg
- assertInteger(subparts[4]); // pss max
- assertInteger(subparts[5]); // uss min
- assertInteger(subparts[6]); // uss avg
- assertInteger(subparts[7]); // uss max
+ assertNonNegativeInteger(subparts[1]); // sample size
+ assertMinAvgMax(subparts[2], subparts[3], subparts[4], checkAvg); // pss
+ assertMinAvgMax(subparts[5], subparts[6], subparts[7], checkAvg); // uss
}
}
- private void checkKills(String[] parts) {
+ private void checkKills(String[] parts, boolean checkAvg) {
assertEquals(7, parts.length);
assertNotNull(parts[1]); // package name
- assertInteger(parts[2]); // uid
- assertInteger(parts[3]); // wakes
- assertInteger(parts[4]); // cpu
- assertInteger(parts[5]); // cached
+ assertNonNegativeInteger(parts[2]); // uid
+ assertNonNegativeInteger(parts[3]); // wakes
+ assertNonNegativeInteger(parts[4]); // cpu
+ assertNonNegativeInteger(parts[5]); // cached
String pssStr = parts[6];
String[] subparts = pssStr.split(":");
assertEquals(3, subparts.length);
- assertInteger(subparts[0]); // pss min
- assertInteger(subparts[1]); // pss avg
- assertInteger(subparts[2]); // pss max
+ assertMinAvgMax(subparts[0], subparts[1], subparts[2], checkAvg); // pss
}
private void checkTotal(String[] parts) {
@@ -319,11 +292,32 @@
String[] subparts = parts[i].split(":");
checkTag(subparts[0], false); // tag
- if (subparts[1].contains("sysmemusage")) {
- break; // see b/18340771
- }
- assertInteger(subparts[1]); // duration (msec)
+ assertNonNegativeInteger(subparts[1]); // duration (msec)
}
}
+ private void checkWithProcStatsApp() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE);
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+ installPackage(DEVICE_SIDE_HELPER_APK, /* grantPermissions= */ true);
+
+ final int helperAppUid = Integer.parseInt(execCommandAndGetFirstGroup(
+ "dumpsys package " + DEVICE_SIDE_HELPER_PACKAGE, "userId=(\\d+)"));
+ CLog.i("Helper app UID: " + helperAppUid);
+
+ try {
+ // Run the device side test which makes some network requests.
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+ "com.android.server.cts.procstats.ProcStatsTest", "testLaunchApp");
+ } finally {
+ if (UNINSTALL_APPS_AFTER_TESTS) {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ getDevice().uninstallPackage(DEVICE_SIDE_HELPER_PACKAGE);
+ }
+ }
+
+ // TODO Check all the lines related to the helper.
+ }
}
diff --git a/hostsidetests/incident/apps/batterystatsapp/Android.mk b/hostsidetests/incident/apps/batterystatsapp/Android.mk
new file mode 100644
index 0000000..b15c0b7
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsBatteryStatsApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
new file mode 100644
index 0000000..36fe872
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.cts.device.batterystats" >
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <service android:name=".SimpleForegroundService" android:exported="true" />
+ <activity android:name=".SimpleActivity" android:label="BatteryStats Test Activity" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.cts.device.batterystats" />
+</manifest>
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java
new file mode 100644
index 0000000..e53c213
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsDeviceTestBase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.cts.device.batterystats;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsDeviceTestBase {
+ private static final String TAG = "BatteryStatsDeviceTest";
+
+ protected Context mContext;
+ protected PowerManager mPowerManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java
new file mode 100644
index 0000000..c619166
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsProcessStateTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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.cts.device.batterystats;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsProcessStateTests extends BatteryStatsDeviceTestBase {
+ private static final String TAG = "BatteryStatsWakeLockTests";
+
+ @Test
+ public void testForegroundService() throws Exception {
+ Intent intent = new Intent();
+ intent.setClass(mContext, SimpleForegroundService.class);
+ Notification notification = new Notification.Builder(mContext, "Foreground Service")
+ .setContentTitle("CTS Foreground")
+ .setSmallIcon(android.R.drawable.ic_secure)
+ .build();
+ mContext.getSystemService(NotificationManager.class).startServiceInForeground(intent,
+ 1, notification);
+ Thread.sleep(3000);
+ }
+
+ @Test
+ public void testActivity() throws Exception {
+ Intent intent = new Intent();
+ intent.setClass(mContext, SimpleActivity.class);
+ mContext.startActivity(intent);
+ }
+}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java
new file mode 100644
index 0000000..9956e79
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsWakeLockTests.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.cts.device.batterystats;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Used by BatteryStatsValidationTest.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsWakeLockTests extends BatteryStatsDeviceTestBase {
+ private static final String TAG = "BatteryStatsWakeLockTests";
+
+ @Test
+ public void testHoldShortWakeLock() throws Exception {
+ PowerManager.WakeLock wl = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "BSShortWakeLock");
+ wl.acquire();
+ Thread.sleep(500);
+ wl.release();
+ }
+
+ @Test
+ public void testHoldLongWakeLock() throws Exception {
+ PowerManager.WakeLock wl = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "BSLongWakeLock");
+ wl.acquire();
+ Thread.sleep(3000);
+ wl.release();
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
similarity index 61%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
index 4e3fc89..3f5bc20 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -11,18 +11,17 @@
* 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
+ * limitations under the License.
*/
-package android.server.cts;
+package com.android.server.cts.device.batterystats;
import android.app.Activity;
-import android.graphics.Rect;
+import android.os.Bundle;
-public class LaunchTapToFinishPipActivity extends Activity {
+public class SimpleActivity extends Activity{
@Override
- protected void onResume() {
- super.onResume();
- PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+ public void onCreate(Bundle bundle) {
+ finish();
}
}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java
new file mode 100644
index 0000000..b8d4507
--- /dev/null
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/SimpleForegroundService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.cts.device.batterystats;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+
+public class SimpleForegroundService extends Service {
+
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ // Sleep for 2 seconds.
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ // Restore interrupt status.
+ Thread.currentThread().interrupt();
+ }
+ // Stop the service using the startId, so that we don't stop
+ // the service in the middle of handling another job
+ stopSelf(msg.arg1);
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ // Start up the thread running the service. Note that we create a
+ // separate thread because the service normally runs in the process's
+ // main thread, which we don't want to block. We also make it
+ // background priority so CPU-intensive work will not disrupt our UI.
+ HandlerThread thread = new HandlerThread("ServiceStartArguments",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ // Get the HandlerThread's Looper and use it for our Handler
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ mServiceHandler.sendMessage(msg);
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/Android.mk b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
new file mode 100644
index 0000000..e9c1555
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
new file mode 100644
index 0000000..312d05d
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.appwidget.cts">
+
+ <application>
+
+ <uses-library android:name="android.test.runner"/>
+
+ <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info" />
+ </receiver>
+
+ <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info" />
+ </receiver>
+
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.appwidget.cts"
+ android:label="CTS Tests for the dumpsys protobuf protocol">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml b/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml
new file mode 100644
index 0000000..3d5d0f8c
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/res/xml/appwidget_info.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="40dp"
+ android:minHeight="40dp"
+ android:minResizeWidth="60dp"
+ android:minResizeHeight="60dp"
+ android:updatePeriodMillis="86400000"
+ android:resizeMode="horizontal|vertical">
+</appwidget-provider>
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java
new file mode 100644
index 0000000..baf2ed3
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/AppWidgetTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 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 android.appwidget.cts;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.cts.provider.FirstAppWidgetProvider;
+import android.appwidget.cts.provider.SecondAppWidgetProvider;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.test.InstrumentationTestCase;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+public class AppWidgetTest extends InstrumentationTestCase {
+
+ private static final String TAG = "AppWidgetTest";
+ private static final int HOST_ID = 1001;
+ private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+ "appwidget grantbind --package android.appwidget.cts --user 0";
+
+ private Context mContext;
+
+ @Override
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getTargetContext();
+ // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+ // Dexmaker is used by mockito.
+ System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
+ }
+
+ public void testBindWidget1() throws Exception {
+ Log.d(TAG, "binding widget 1 ...");
+ setupWidget(getFirstAppWidgetProviderInfo(), null, true);
+ Log.d(TAG, "binding widget 1 done");
+ }
+
+ public void testBindWidget2() throws Exception {
+ Log.d(TAG, "binding widget 2 ...");
+ Bundle options = new Bundle();
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 1);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 2);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 3);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 4);
+ setupWidget(getSecondAppWidgetProviderInfo(), options, true);
+ Log.d(TAG, "binding widget 2 done");
+ }
+
+ public void testAllocateOnlyWidget1() throws Exception {
+ Log.d(TAG, "allocating widget 1 ...");
+ setupWidget(getFirstAppWidgetProviderInfo(), null, false);
+ Log.d(TAG, "allocating widget 1 done");
+ }
+
+ public void testCleanup() throws Exception {
+ Log.d(TAG, "deleting host");
+ grantBindAppWidgetPermission();
+ AppWidgetHost host = new AppWidgetHost(mContext, HOST_ID);
+ host.deleteHost();
+ }
+
+ private void setupWidget(AppWidgetProviderInfo prov, Bundle options, boolean bindWidget) throws Exception {
+ // We want to bind widgets.
+ grantBindAppWidgetPermission();
+
+ // Create a host and start listening.
+ AppWidgetHost host = new AppWidgetHost(mContext, HOST_ID);
+ host.deleteHost();
+ host.startListening();
+
+ AppWidgetManager mgr = getAppWidgetManager();
+
+ // Initially we have no widgets.
+ assertEquals(0, mgr.getAppWidgetIds(prov.provider).length);
+
+ // Allocate the first widget id to bind.
+ int appWidgetId = host.allocateAppWidgetId();
+
+ if (bindWidget) {
+ // Bind the widget.
+ getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+ prov.getProfile(), prov.provider, options);
+
+ assertEquals(1, mgr.getAppWidgetIds(prov.provider).length);
+ }
+ }
+
+ private ComponentName getFirstWidgetComponent() {
+ return new ComponentName(mContext.getPackageName(),
+ FirstAppWidgetProvider.class.getName());
+ }
+
+ private ComponentName getSecondWidgetComponent() {
+ return new ComponentName(mContext.getPackageName(),
+ SecondAppWidgetProvider.class.getName());
+ }
+
+ private ArrayList<String> runShellCommand(String command) throws Exception {
+ ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+ .executeShellCommand(command);
+
+ ArrayList<String> ret = new ArrayList<>();
+ // Read the input stream fully.
+ try (BufferedReader r = new BufferedReader(
+ new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd)))) {
+ String line;
+ while ((line = r.readLine()) != null) {
+ ret.add(line);
+ }
+ }
+ return ret;
+ }
+
+ private AppWidgetProviderInfo getFirstAppWidgetProviderInfo() {
+ return getProviderInfo(getFirstWidgetComponent());
+ }
+
+ private AppWidgetProviderInfo getSecondAppWidgetProviderInfo() {
+ return getProviderInfo(getSecondWidgetComponent());
+ }
+
+ private AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
+ List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+
+ final int providerCount = providers.size();
+ for (int i = 0; i < providerCount; i++) {
+ AppWidgetProviderInfo provider = providers.get(i);
+ if (componentName.equals(provider.provider)
+ && Process.myUserHandle().equals(provider.getProfile())) {
+ return provider;
+
+ }
+ }
+
+ return null;
+ }
+
+ private AppWidgetManager getAppWidgetManager() {
+ return (AppWidgetManager) getInstrumentation().getTargetContext()
+ .getSystemService(Context.APPWIDGET_SERVICE);
+ }
+
+ private void grantBindAppWidgetPermission() throws Exception {
+ runShellCommand(GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+ }
+
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
new file mode 100644
index 0000000..e3ffa8f
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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 android.appwidget.cts.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public final class FirstAppWidgetProvider extends BroadcastReceiver {
+
+ public void onReceive(Context context, Intent data) {
+ }
+
+}
diff --git a/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
new file mode 100644
index 0000000..c7dc1c9
--- /dev/null
+++ b/hostsidetests/incident/apps/boundwidgetapp/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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 android.appwidget.cts.provider;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public final class SecondAppWidgetProvider extends BroadcastReceiver {
+
+ public void onReceive(Context context, Intent data) {
+ }
+
+}
diff --git a/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java b/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
index 075a393..d0af01b 100644
--- a/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
+++ b/hostsidetests/incident/apps/netstatsapp/src/com/android/server/cts/netstats/NetstatsDeviceTest.java
@@ -15,8 +15,7 @@
*/
package com.android.server.cts.netstats;
-import static junit.framework.Assert.assertEquals;
-
+import android.net.TrafficStats;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
@@ -34,12 +33,27 @@
public class NetstatsDeviceTest {
private static final String TAG = "NetstatsDeviceTest";
- @Test
- public void testDoNetwork() throws Exception {
- Log.i(TAG, "Making network requests...");
+ private static final int NET_TAG = 123123123;
+ @Test
+ public void testDoNetworkWithoutTagging() throws Exception {
+ Log.i(TAG, "testDoNetworkWithoutTagging");
+
+ makeNetworkRequest();
+ }
+
+ @Test
+ public void testDoNetworkWithTagging() throws Exception {
+ Log.i(TAG, "testDoNetworkWithTagging");
+
+ TrafficStats.getAndSetThreadStatsTag(NET_TAG);
+ makeNetworkRequest();
+ }
+
+ private void makeNetworkRequest() throws Exception {
final URL url = new URL("http://www.android.com/");
final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+ HttpURLConnection.setFollowRedirects(true);
try {
final int status = urlConnection.getResponseCode();
diff --git a/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java
new file mode 100644
index 0000000..9ffe5c4
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/AppWidgetIncidentTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 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.cts;
+
+import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.WidgetProto;
+
+/**
+ * Test to check that the appwidget service properly outputs its dump state.
+ */
+public class AppWidgetIncidentTest extends ProtoDumpTestCase {
+
+ private static final String DEVICE_TEST_CLASS = ".AppWidgetTest";
+ private static final String DEVICE_SIDE_TEST_APK = "CtsAppWidgetApp.apk";
+ private static final String DEVICE_SIDE_TEST_PACKAGE = "android.appwidget.cts";
+ private static final String DEVICE_SIDE_WIDGET_CLASS_1 =
+ "android.appwidget.cts.provider.FirstAppWidgetProvider";
+ private static final String DEVICE_SIDE_WIDGET_CLASS_2 =
+ "android.appwidget.cts.provider.SecondAppWidgetProvider";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ super.tearDown();
+ }
+
+ private boolean hasAppWidgets() throws Exception {
+ return getDevice().hasFeature("android.software.app_widgets");
+ }
+
+ public void testAppWidgetProtoDump_firstComponent() throws Exception {
+ if (!hasAppWidgets()) {
+ return;
+ }
+
+ WidgetProto widget = prepare("testBindWidget1");
+
+ assertNotNull(widget);
+ assertEquals(DEVICE_SIDE_TEST_PACKAGE,
+ widget.getProviderPackage());
+ assertEquals(DEVICE_SIDE_WIDGET_CLASS_1,
+ widget.getProviderClass());
+ assertEquals(false, widget.getIsCrossProfile());
+ assertEquals(false, widget.getIsHostStopped());
+ assertEquals(0, widget.getMinWidth());
+ assertEquals(0, widget.getMinHeight());
+ assertEquals(0, widget.getMaxWidth());
+ assertEquals(0, widget.getMaxHeight());
+
+ cleanup();
+ }
+
+ public void testAppWidgetProtoDump_secondComponent() throws Exception {
+ if (!hasAppWidgets()) {
+ return;
+ }
+
+ WidgetProto widget = prepare("testBindWidget2");
+
+ assertNotNull(widget);
+ assertEquals(DEVICE_SIDE_TEST_PACKAGE,
+ widget.getProviderPackage());
+ assertEquals(DEVICE_SIDE_WIDGET_CLASS_2,
+ widget.getProviderClass());
+ assertEquals(false, widget.getIsCrossProfile());
+ assertEquals(false, widget.getIsHostStopped());
+ assertEquals(1, widget.getMinWidth());
+ assertEquals(2, widget.getMinHeight());
+ assertEquals(3, widget.getMaxWidth());
+ assertEquals(4, widget.getMaxHeight());
+
+ cleanup();
+ }
+
+ public void testAppWidgetProtoDump_firstComponentNotBound() throws Exception {
+ if (!hasAppWidgets()) {
+ return;
+ }
+
+ WidgetProto widget = prepare("testAllocateOnlyWidget1");
+
+ // a widget that is not bound must not show up in the dump
+ assertNull(widget);
+
+ cleanup();
+ }
+
+ private WidgetProto prepare(String testMethodName) throws Exception {
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_TEST_CLASS, testMethodName);
+
+ AppWidgetServiceDumpProto dump = getDump(AppWidgetServiceDumpProto.parser(),
+ "dumpsys appwidget --proto");
+
+ for (WidgetProto widgetProto : dump.getWidgetsList()) {
+ if (DEVICE_SIDE_TEST_PACKAGE.equals(widgetProto.getHostPackage())) {
+ return widgetProto;
+ }
+ }
+ return null;
+ }
+
+ private void cleanup() throws Exception {
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_TEST_CLASS, "testCleanup");
+ }
+
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
new file mode 100644
index 0000000..ccd301a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.cts;
+
+import android.service.battery.BatteryServiceDumpProto;
+
+import java.util.Scanner;
+
+/**
+ * Test to check that the battery manager properly outputs its dump state.
+ */
+public class BatteryIncidentTest extends ProtoDumpTestCase {
+ public void testBatteryServiceDump() throws Exception {
+ final BatteryServiceDumpProto dump = getDump(BatteryServiceDumpProto.parser(),
+ "dumpsys battery --proto");
+
+ assertTrue(dump.getPlugged()!=BatteryServiceDumpProto.BatteryPlugged.BATTERY_PLUGGED_WIRELESS);
+ assertTrue(dump.getMaxChargingCurrent() > 0);
+ assertTrue(dump.getMaxChargingVoltage() > 0);
+ assertTrue(dump.getChargeCounter() > 0);
+ assertTrue(dump.getStatus()!=BatteryServiceDumpProto.BatteryStatus.BATTERY_STATUS_INVALID);
+ assertTrue(dump.getHealth()!=BatteryServiceDumpProto.BatteryHealth.BATTERY_HEALTH_INVALID);
+ int scale = dump.getScale();
+ assertTrue(scale > 0);
+ int level = dump.getLevel();
+ assertTrue(level >= 0 && level <= scale);
+ assertTrue(dump.getVoltage() > 0);
+ assertTrue(dump.getTemperature() > 0);
+ assertNotNull(dump.getTechnology());
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
new file mode 100644
index 0000000..08c79bd
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 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.cts;
+
+
+import com.android.tradefed.log.LogUtil;
+
+/**
+ * Test for "dumpsys batterystats -c
+ *
+ * Validates reporting of battery stats based on different events
+ */
+public class BatteryStatsValidationTest extends ProtoDumpTestCase {
+ private static final String TAG = "BatteryStatsValidationTest";
+
+ private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
+ private static final String DEVICE_SIDE_TEST_PACKAGE
+ = "com.android.server.cts.device.batterystats";
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+
+ batteryOffScreenOn();
+ super.tearDown();
+ }
+
+ protected void batteryOnScreenOff() throws Exception {
+ getDevice().executeShellCommand("dumpsys battery unplug");
+ getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+ }
+
+ protected void batteryOffScreenOn() throws Exception {
+ getDevice().executeShellCommand("dumpsys battery reset");
+ getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+ }
+
+ public void testWakeLockDuration() throws Exception {
+ batteryOnScreenOff();
+
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
+ "testHoldShortWakeLock");
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsWakeLockTests",
+ "testHoldLongWakeLock");
+
+ assertValueRange("wl", "BSShortWakeLock", 14, (long) (500 * 0.9), 500 * 2);
+ assertValueRange("wl", "BSLongWakeLock", 14, (long) (3000 * 0.9), 3000 * 2);
+
+ batteryOffScreenOn();
+ }
+
+ public void testServiceForegroundDuration() throws Exception {
+ batteryOnScreenOff();
+ installPackage(DEVICE_SIDE_TEST_APK, true);
+
+ getDevice().executeShellCommand(
+ "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
+ assertValueRange("st", "", 5, 0, 0); // No foreground service time before test
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".BatteryStatsProcessStateTests",
+ "testForegroundService");
+ assertValueRange("st", "", 5, (long) (2000 * 0.8), 4000);
+
+ batteryOffScreenOn();
+ }
+
+ /**
+ * Verifies that the recorded time for the specified tag and name in the test package
+ * is within the specified range
+ * @throws Exception
+ */
+ private void assertValueRange(String tag, String optionalAfterTag,
+ int index, long min, long max) throws Exception {
+ String dumpsys = getDevice().executeShellCommand("dumpsys batterystats --checkin");
+ String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
+ + DEVICE_SIDE_TEST_PACKAGE);
+ String[] uidLineParts = uidLine.split(":");
+ // 3rd entry is package uid
+ assertTrue(uidLineParts.length > 2);
+ int uid = Integer.parseInt(uidLineParts[2].trim());
+ assertTrue(uid > 10000);
+
+ String[] lines = dumpsys.split("\n");
+ long time = 0;
+ for (int i = lines.length - 1; i >= 0; i--) {
+ String line = lines[i];
+ if (line.contains(uid + ",l," + tag + "," + optionalAfterTag)) {
+ String[] wlParts = line.split(",");
+ //System.err.println(line);
+ time = Long.parseLong(wlParts[index]);
+ }
+ }
+ assertTrue("Value not greater than min", time >= min);
+ assertTrue("Value not less than max", time <= max);
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
index 48fcca0..397e598 100644
--- a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
@@ -24,7 +24,11 @@
import android.service.NetworkStatsRecorderProto;
import android.service.NetworkStatsServiceDumpProto;
+import com.android.tradefed.log.LogUtil.CLog;
+
import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Test for "dumpsys netstats --proto"
@@ -50,32 +54,6 @@
super.tearDown();
}
- /**
- * Parse the output of "dumpsys netstats --proto" and make sure all the values are probable.
- */
- public void testSanityCheck() throws Exception {
- installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
-
- // Run the device side test which makes some network requests.
- runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, null, null);
-
- // Also does ping for more network activity.
- getDevice().executeShellCommand("ping -c 8 -i 0 8.8.8.8");
-
- // Force refresh the output.
- getDevice().executeShellCommand("dumpsys netstats --poll");
-
- final NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(),
- "dumpsys netstats --proto");
-
- checkInterfaces(dump.getActiveInterfacesList());
- checkInterfaces(dump.getActiveUidInterfacesList());
-
- checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false);
- checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false);
- checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false);
- checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true);
- }
private void assertPositive(String name, long value) {
if (value > 0) return;
@@ -87,6 +65,191 @@
fail(name + " expected to be zero or positive, but was: " + value);
}
+ private void assertGreaterOrEqual(long greater, long lesser) {
+ assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
+ greater >= lesser);
+ }
+
+ /**
+ * Parse the output of "dumpsys netstats --proto" and make sure all the values are probable.
+ */
+ public void testSanityCheck() throws Exception {
+
+ final long st = System.currentTimeMillis();
+
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+
+ // Find the package UID.
+ final int uid = Integer.parseInt(execCommandAndGetFirstGroup(
+ "dumpsys package " + DEVICE_SIDE_TEST_PACKAGE, "userId=(\\d+)"));
+
+ CLog.i("Start time: " + st);
+ CLog.i("App UID: " + uid);
+
+ // Run the device side test which makes some network requests.
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, null, null);
+
+ // Make some more activity.
+ getDevice().executeShellCommand("ping -s 100 -c 10 -i 0 www.android.com");
+
+ // Force refresh the output.
+ getDevice().executeShellCommand("dumpsys netstats --poll");
+
+ NetworkStatsServiceDumpProto dump = getDump(NetworkStatsServiceDumpProto.parser(),
+ "dumpsys netstats --proto");
+
+ CLog.d("First dump:\n" + dump.toString());
+
+ // Basic sanity check.
+ checkInterfaces(dump.getActiveInterfacesList());
+ checkInterfaces(dump.getActiveUidInterfacesList());
+
+ checkStats(dump.getDevStats(), /*withUid=*/ false, /*withTag=*/ false);
+ checkStats(dump.getXtStats(), /*withUid=*/ false, /*withTag=*/ false);
+ checkStats(dump.getUidStats(), /*withUid=*/ true, /*withTag=*/ false);
+ checkStats(dump.getUidTagStats(), /*withUid=*/ true, /*withTag=*/ true);
+
+ // Remember the original values.
+ final Predicate<NetworkStatsCollectionKeyProto> uidFilt = key -> key.getUid() == uid;
+ final Predicate<NetworkStatsCollectionKeyProto> tagFilt =
+ key -> (key.getTag() == 123123123) && (key.getUid() == uid);
+
+ final long devRxPackets = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+ final long devRxBytes = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+ final long devTxPackets = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+ final long devTxBytes = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+ final long xtRxPackets = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+ final long xtRxBytes = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+ final long xtTxPackets = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+ final long xtTxBytes = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+ final long uidRxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+ final long uidRxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+ final long uidTxPackets = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+ final long uidTxBytes = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+ final long tagRxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+ final long tagRxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+ final long tagTxPackets = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+ final long tagTxBytes = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+ // Run again to make some more activity.
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+ "com.android.server.cts.netstats.NetstatsDeviceTest",
+ "testDoNetworkWithoutTagging");
+
+ getDevice().executeShellCommand("dumpsys netstats --poll");
+ dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto");
+
+ CLog.d("Second dump:\n" + dump.toString());
+
+ final long devRxPackets2 = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+ final long devRxBytes2 = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+ final long devTxPackets2 = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+ final long devTxBytes2 = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+ final long xtRxPackets2 = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+ final long xtRxBytes2 = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+ final long xtTxPackets2 = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+ final long xtTxBytes2 = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+ final long uidRxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+ final long uidRxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+ final long uidTxPackets2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+ final long uidTxBytes2 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+ final long tagRxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+ final long tagRxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+ final long tagTxPackets2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+ final long tagTxBytes2 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+ // At least 1 packet, 100 bytes sent.
+ assertGreaterOrEqual(uidTxPackets2, uidTxPackets + 1);
+ assertGreaterOrEqual(uidTxBytes2, uidTxBytes + 100);
+
+// assertGreaterOrEqual(tagTxPackets2, tagTxPackets + 1);
+// assertGreaterOrEqual(tagTxBytes2, tagTxBytes + 100);
+
+ // At least 2 packets, 100 bytes sent.
+ assertGreaterOrEqual(uidRxPackets2, uidRxPackets + 2);
+ assertGreaterOrEqual(uidRxBytes2, uidRxBytes + 100);
+
+// assertGreaterOrEqual(tagRxPackets2, tagRxPackets + 2);
+// assertGreaterOrEqual(tagRxBytes2, tagRxBytes + 100);
+
+ // Run again to make some more activity.
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+ "com.android.server.cts.netstats.NetstatsDeviceTest",
+ "testDoNetworkWithTagging");
+
+ getDevice().executeShellCommand("dumpsys netstats --poll");
+ dump = getDump(NetworkStatsServiceDumpProto.parser(), "dumpsys netstats --proto");
+
+ CLog.d("Second dump:\n" + dump.toString());
+
+ final long devRxPackets3 = sum(dump.getDevStats(), st, b -> b.getRxPackets());
+ final long devRxBytes3 = sum(dump.getDevStats(), st, b -> b.getRxBytes());
+ final long devTxPackets3 = sum(dump.getDevStats(), st, b -> b.getTxPackets());
+ final long devTxBytes3 = sum(dump.getDevStats(), st, b -> b.getTxBytes());
+
+ final long xtRxPackets3 = sum(dump.getXtStats(), st, b -> b.getRxPackets());
+ final long xtRxBytes3 = sum(dump.getXtStats(), st, b -> b.getRxBytes());
+ final long xtTxPackets3 = sum(dump.getXtStats(), st, b -> b.getTxPackets());
+ final long xtTxBytes3 = sum(dump.getXtStats(), st, b -> b.getTxBytes());
+
+ final long uidRxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxPackets());
+ final long uidRxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getRxBytes());
+ final long uidTxPackets3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxPackets());
+ final long uidTxBytes3 = sum(dump.getUidStats(), st, uidFilt, b -> b.getTxBytes());
+
+ final long tagRxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxPackets());
+ final long tagRxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getRxBytes());
+ final long tagTxPackets3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxPackets());
+ final long tagTxBytes3 = sum(dump.getUidTagStats(), st, tagFilt, b -> b.getTxBytes());
+
+ // At least 1 packet, 100 bytes sent.
+ assertGreaterOrEqual(uidTxPackets3, uidTxPackets2 + 1);
+ assertGreaterOrEqual(uidTxBytes3, uidTxBytes2 + 100);
+
+ assertGreaterOrEqual(tagTxPackets3, tagTxPackets2 + 1);
+ assertGreaterOrEqual(tagTxBytes3, tagTxBytes2 + 100);
+
+ // At least 2 packets, 100 bytes sent.
+ assertGreaterOrEqual(uidRxPackets3, uidRxPackets2 + 2);
+ assertGreaterOrEqual(uidRxBytes3, uidRxBytes2 + 100);
+
+ assertGreaterOrEqual(tagRxPackets3, tagRxPackets2 + 2);
+ assertGreaterOrEqual(tagRxBytes3, tagRxBytes2 + 100);
+ }
+
+ private long sum(NetworkStatsRecorderProto recorder,
+ long startTime,
+ Function<NetworkStatsHistoryBucketProto, Long> func) {
+ return sum(recorder, startTime, key -> true, func);
+ }
+
+ private long sum(NetworkStatsRecorderProto recorder,
+ long startTime,
+ Predicate<NetworkStatsCollectionKeyProto> filter,
+ Function<NetworkStatsHistoryBucketProto, Long> func) {
+
+ long total = 0;
+ for (NetworkStatsCollectionStatsProto stats
+ : recorder.getCompleteHistory().getStatsList()) {
+ if (!filter.test(stats.getKey())) {
+ continue;
+ }
+ for (NetworkStatsHistoryBucketProto bucket : stats.getHistory().getBucketsList()) {
+ if (startTime < bucket.getBucketStartMs()) {
+ continue;
+ }
+ total += func.apply(bucket);
+ }
+ }
+ return total;
+ }
+
private void checkInterfaces(List<NetworkInterfaceProto> interfaces) {
/* Example:
active_interfaces=[
@@ -128,7 +291,7 @@
assertFalse("There must be at least one non-metered interface during CTS", allMetered);
}
- private void checkStats(NetworkStatsRecorderProto record, boolean withUid, boolean withTag) {
+ private void checkStats(NetworkStatsRecorderProto recorder, boolean withUid, boolean withTag) {
/*
* Example:
dev_stats=NetworkStatsRecorderProto {
@@ -173,9 +336,9 @@
}
*/
- assertNotNegative("Pending bytes", record.getPendingTotalBytes());
+ assertNotNegative("Pending bytes", recorder.getPendingTotalBytes());
- for (NetworkStatsCollectionStatsProto stats : record.getCompleteHistory().getStatsList()) {
+ for (NetworkStatsCollectionStatsProto stats : recorder.getCompleteHistory().getStatsList()) {
final NetworkStatsCollectionKeyProto key = stats.getKey();
@@ -208,8 +371,8 @@
assertNotNegative("TX bytes", bucket.getTxBytes());
assertNotNegative("TX packets", bucket.getTxPackets());
- // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
- final long FACTOR = 10;
+// 10 was still too big? // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
+ final long FACTOR = 4;
assertTrue(
String.format("# of bytes %d too small for # of packets %d",
bucket.getRxBytes(), bucket.getRxPackets()),
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
new file mode 100644
index 0000000..dac0672
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.cts;
+
+import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationServiceDumpProto;
+import android.service.notification.State;
+
+/**
+ * Test to check that the notification service properly outputs its dump state.
+ */
+public class NotificationTest extends ProtoDumpTestCase {
+ /**
+ * Tests that at least one notification is posted, and verify its properties are plausible.
+ */
+ public void testNotificationRecords() throws Exception {
+ final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+ "dumpsys notification --proto");
+
+ assertTrue(dump.getRecordsCount() > 0);
+ boolean found = false;
+ for (NotificationRecordProto record : dump.getRecordsList()) {
+ if (record.getKey().contains("android")) {
+ found = true;
+ assertEquals(State.POSTED, record.getState());
+ assertTrue(record.getImportance() > 0 /* NotificationManager.IMPORTANCE_NONE */);
+ assertEquals(record.getKey(), record.getGroupKey());
+
+ // Ensure these fields exist, at least
+ record.getFlags();
+ record.getChannelId();
+ record.getSound();
+ record.getSoundUsage();
+ record.getCanVibrate();
+ record.getCanShowLight();
+ record.getGroupKey();
+ }
+ }
+
+ assertTrue(found);
+ }
+}
+
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 0ec59c2..09734bb 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -24,6 +24,7 @@
import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.CollectingTestListener;
@@ -36,6 +37,8 @@
import java.io.FileNotFoundException;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -141,4 +144,27 @@
throw new AssertionError(errorBuilder.toString());
}
}
+
+ /**
+ * Execute the given command, and find the given pattern and return the resulting
+ * {@link Matcher}.
+ */
+ protected Matcher execCommandAndFind(String command, String pattern) throws Exception {
+ final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ getDevice().executeShellCommand(command, receiver);
+ final String output = receiver.getOutput();
+ final Matcher matcher = Pattern.compile(pattern).matcher(output);
+ assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
+ return matcher;
+ }
+
+ /**
+ * Execute the given command, find the given pattern, and return the first captured group
+ * as a String.
+ */
+ protected String execCommandAndGetFirstGroup(String command, String pattern) throws Exception {
+ final Matcher matcher = execCommandAndFind(command, pattern);
+ assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
+ return matcher.group(1);
+ }
}
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index f59cba1..aa54075 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -196,7 +196,8 @@
* Sends a system notification containing actions with pending intents to launch the app's
* main activitiy or service.
*/
- static void sendNotification(Context context, int notificationId, String notificationType ) {
+ static void sendNotification(Context context, String channelId, int notificationId,
+ String notificationType ) {
Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
final Intent serviceIntent = new Intent(context, MyService.class);
final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
@@ -204,7 +205,7 @@
final Bundle bundle = new Bundle();
bundle.putCharSequence("parcelable", "I am not");
- final Notification.Builder builder = new Notification.Builder(context)
+ final Notification.Builder builder = new Notification.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification);
Action action = null;
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
index b88c45d..fa3fdd1 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
@@ -18,6 +18,8 @@
import static com.android.cts.net.hostside.app2.Common.TAG;
import android.R;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
@@ -27,7 +29,7 @@
* Service used to change app state to FOREGROUND_SERVICE.
*/
public class MyForegroundService extends Service {
-
+ private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
private static final int FLAG_START_FOREGROUND = 1;
private static final int FLAG_STOP_FOREGROUND = 2;
@@ -39,10 +41,14 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
switch (intent.getFlags()) {
case FLAG_START_FOREGROUND:
Log.d(TAG, "Starting foreground");
- startForeground(42, new Notification.Builder(this)
+ startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
.build());
break;
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 9c19e50..2496c4a 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -20,6 +20,8 @@
import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
import static com.android.cts.net.hostside.app2.Common.TAG;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +38,7 @@
* Service used to dynamically register a broadcast receiver.
*/
public class MyService extends Service {
+ private static final String NOTIFICATION_CHANNEL_ID = "MyService";
private MyBroadcastReceiver mReceiver;
@@ -75,8 +78,8 @@
@Override
public void sendNotification(int notificationId, String notificationType) {
- MyBroadcastReceiver
- .sendNotification(getApplicationContext(), notificationId, notificationType);
+ MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
+ notificationId, notificationType);
}
};
@@ -86,7 +89,18 @@
}
@Override
+ public void onCreate() {
+ final Context context = getApplicationContext();
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
+ }
+
+ @Override
public void onDestroy() {
+ final Context context = getApplicationContext();
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
if (mReceiver != null) {
Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
getApplicationContext().unregisterReceiver(mReceiver);
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 7452d22..a680123 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -90,7 +90,7 @@
STATIC_LIB_CONSUMER1_APK), false, false));
// Try to load code and resources
runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
- "android.os.lib.consumer1.UseSharedLibraryTest",
+ "android.os.lib.consumer1.CookieTest",
"testLoadCodeAndResources");
} finally {
getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
@@ -205,7 +205,7 @@
STATIC_LIB_CONSUMER2_APK), false, false));
// Ensure code and resources can be loaded
runDeviceTests(STATIC_LIB_CONSUMER2_PKG,
- "android.os.lib.consumer2.UseSharedLibraryTest",
+ "android.os.lib.consumer2.CookieTest",
"testLoadCodeAndResources");
} finally {
getDevice().uninstallPackage(STATIC_LIB_CONSUMER2_PKG);
@@ -270,7 +270,7 @@
STATIC_LIB_CONSUMER2_APK), false, false));
// Ensure libraries are properly reported
runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
- "android.os.lib.consumer1.UseSharedLibraryTest",
+ "android.os.lib.consumer1.CookieTest",
"testSharedLibrariesProperlyReported");
} finally {
getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
@@ -297,7 +297,7 @@
STATIC_LIB_CONSUMER1_APK), false, false));
// Ensure the client can see only the lib it depends on
runDeviceTests(STATIC_LIB_CONSUMER1_PKG,
- "android.os.lib.consumer1.UseSharedLibraryTest",
+ "android.os.lib.consumer1.CookieTest",
"testAppCanSeeOnlyLibrariesItDependOn");
} finally {
getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index 2704850..ffb5adde 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -40,7 +40,7 @@
selinux_plat_file_contexts := $(call intermediates-dir-for,ETC,plat_file_contexts)/plat_file_contexts
-selinux_general_property_contexts := $(call intermediates-dir-for,ETC,general_property_contexts)/general_property_contexts
+selinux_plat_property_contexts := $(call intermediates-dir-for,ETC,plat_property_contexts)/plat_property_contexts
selinux_plat_service_contexts := $(call intermediates-dir-for,ETC,plat_service_contexts)/plat_service_contexts
@@ -49,8 +49,8 @@
$(HOST_OUT_EXECUTABLES)/checkfc \
$(selinux_plat_seapp_contexts) \
$(selinux_plat_seapp_neverallows) \
- $(selinux_general_file_contexts) \
- $(selinux_general_property_contexts) \
+ $(selinux_plat_file_contexts) \
+ $(selinux_plat_property_contexts) \
$(selinux_plat_service_contexts)
selinux_general_policy := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_10.java b/hostsidetests/security/src/android/security/cts/Poc16_10.java
index 94b442d..d04ebea 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_10.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_10.java
@@ -68,6 +68,7 @@
/**
* b/30906694
*/
+ @SecurityTest
public void testPocCVE_2016_6733() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-6733", getDevice(), 60);
@@ -77,6 +78,7 @@
/**
* b/30907120
*/
+ @SecurityTest
public void testPocCVE_2016_6734() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-6734", getDevice(), 60);
@@ -86,6 +88,7 @@
/**
* b/30907701
*/
+ @SecurityTest
public void testPocCVE_2016_6735() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-6735", getDevice(), 60);
@@ -95,6 +98,7 @@
/**
* b/30953284
*/
+ @SecurityTest
public void testPocCVE_2016_6736() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-6736", getDevice(), 60);
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_12.java b/hostsidetests/security/src/android/security/cts/Poc16_12.java
index d0d25f0..a6160d5 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_12.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_12.java
@@ -59,6 +59,7 @@
/**
* b/31799206
*/
+ @SecurityTest
public void testPocCVE_2016_8426() throws Exception {
if(containsDriver(getDevice(), "/dev/nvhost-gpu")) {
AdbUtils.runPoc("CVE-2016-8426", getDevice(), 60);
@@ -68,6 +69,7 @@
/**
* b/31799885
*/
+ @SecurityTest
public void testPocCVE_2016_8427() throws Exception {
if(containsDriver(getDevice(), "/dev/nvhost-gpu") ||
containsDriver(getDevice(), "/dev/nvhost-dbg-gpu")) {
@@ -78,6 +80,7 @@
/**
* b/31993456
*/
+ @SecurityTest
public void testPocCVE_2016_8428() throws Exception {
if(containsDriver(getDevice(), "/dev/nvmap")) {
AdbUtils.runPoc("CVE-2016-8428", getDevice(), 60);
@@ -87,6 +90,7 @@
/**
* b/32160775
*/
+ @SecurityTest
public void testPocCVE_2016_8429() throws Exception {
if(containsDriver(getDevice(), "/dev/nvmap")) {
AdbUtils.runPoc("CVE-2016-8429", getDevice(), 60);
@@ -96,6 +100,7 @@
/**
* b/32225180
*/
+ @SecurityTest
public void testPocCVE_2016_8430() throws Exception {
if(containsDriver(getDevice(), "/dev/nvhost-vic")) {
AdbUtils.runPoc("CVE-2016-8430", getDevice(), 60);
@@ -105,6 +110,7 @@
/**
* b/32402179
*/
+ @SecurityTest
public void testPocCVE_2016_8431() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-8431", getDevice(), 60);
@@ -114,6 +120,7 @@
/**
* b/32447738
*/
+ @SecurityTest
public void testPocCVE_2016_8432() throws Exception {
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
AdbUtils.runPoc("CVE-2016-8432", getDevice(), 60);
@@ -123,6 +130,7 @@
/**
* b/32125137
*/
+ @SecurityTest
public void testPocCVE_2016_8434() throws Exception {
if(containsDriver(getDevice(), "/dev/kgsl-3d0")) {
AdbUtils.runPoc("CVE-2016-8434", getDevice(), 60);
@@ -132,6 +140,7 @@
/**
* b/32700935
*/
+ @SecurityTest
public void testPocCVE_2016_8435() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
@@ -142,6 +151,7 @@
/**
* b/31568617
*/
+ @SecurityTest
public void testPocCVE_2016_9120() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/ion")) {
@@ -153,6 +163,7 @@
/**
* b/31225246
*/
+ @SecurityTest
public void testPocCVE_2016_8412() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/v4l-subdev7")) {
@@ -163,6 +174,7 @@
/**
* b/31243641
*/
+ @SecurityTest
public void testPocCVE_2016_8444() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/v4l-subdev17")) {
@@ -173,6 +185,7 @@
/**
* b/31791148
*/
+ @SecurityTest
public void testPocCVE_2016_8448() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/graphics/fb0")) {
@@ -183,6 +196,7 @@
/**
* b/31798848
*/
+ @SecurityTest
public void testPocCVE_2016_8449() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/dev/tegra_avpchannel")) {
@@ -193,6 +207,7 @@
/**
* b/31668540
*/
+ @SecurityTest
public void testPocCVE_2016_8460() throws Exception {
if(containsDriver(getDevice(), "/dev/nvmap")) {
String result = AdbUtils.runPoc("CVE-2016-8460", getDevice(), 60);
@@ -203,6 +218,7 @@
/**
* b/32402548
*/
+ @SecurityTest
public void testPocCVE_2017_0403() throws Exception {
enableAdbRoot(getDevice());
AdbUtils.runPoc("CVE-2017-0403", getDevice(), 60);
@@ -211,6 +227,7 @@
/**
* b/32510733
*/
+ @SecurityTest
public void testPocCVE_2017_0404() throws Exception {
enableAdbRoot(getDevice());
if(containsDriver(getDevice(), "/proc/asound/version")) {
@@ -221,6 +238,7 @@
/**
* b/32178033
*/
+ @SecurityTest
public void testPocCVE_2016_8451() throws Exception {
enableAdbRoot(getDevice());
String command =
@@ -231,6 +249,7 @@
/**
* b/32659848
*/
+ @SecurityTest
public void testPoc32659848() throws Exception {
String command =
"echo 18014398509481980 > /sys/kernel/debug/tracing/buffer_size_kb";
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_01.java b/hostsidetests/security/src/android/security/cts/Poc17_01.java
index 88470b6..f8ed22a 100644
--- a/hostsidetests/security/src/android/security/cts/Poc17_01.java
+++ b/hostsidetests/security/src/android/security/cts/Poc17_01.java
@@ -34,6 +34,7 @@
/**
* b/32636619
*/
+ @SecurityTest
public void testPocCVE_2017_0429() throws Exception {
if(containsDriver(getDevice(), "/dev/nvhost-as-gpu")) {
enableAdbRoot(getDevice());
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index b3f4ad7..1799fb8 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -347,12 +347,12 @@
/* obtain property_contexts file from running device */
devicePcFile = File.createTempFile("property_contexts", ".tmp");
devicePcFile.deleteOnExit();
- mDevice.pullFile("/property_contexts", devicePcFile);
+ mDevice.pullFile("/plat_property_contexts", devicePcFile);
/* retrieve the AOSP property_contexts file from jar */
- aospPcFile = copyResourceToTempFile("/general_property_contexts");
+ aospPcFile = copyResourceToTempFile("/plat_property_contexts");
- assertFileStartsWith(aospPcFile, devicePcFile);
+ assertFileEquals(aospPcFile, devicePcFile);
}
/**
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
index 1c4050c..b091bae 100755
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
@@ -29,6 +29,12 @@
android:resizeableActivity="true"
android:exported="true"
/>
+ <activity android:name=".ResumeWhilePausingActivity"
+ android:allowEmbedded="true"
+ android:resumeWhilePausing="true"
+ android:taskAffinity=""
+ android:exported="true"
+ />
<activity android:name=".ResizeableActivity"
android:resizeableActivity="true"
android:exported="true"
@@ -99,7 +105,7 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
/>
- <activity android:name=".LaunchTapToFinishPipActivity"
+ <activity android:name=".LaunchEnterPipActivity"
android:resizeableActivity="false"
android:supportsPictureInPicture="true"
androidprv:alwaysFocusable="true"
@@ -258,6 +264,10 @@
android:screenOrientation="landscape"
android:documentLaunchMode="always"
/>
+ <activity android:name=".MoveTaskToBackActivity"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ />
<receiver
android:name=".LaunchBroadcastReceiver"
android:enabled="true"
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
index 6dab9dd..5eb6514 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.util.Log;
import android.view.WindowManager;
/**
@@ -33,6 +34,7 @@
public class BroadcastReceiverActivity extends Activity {
public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+ private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
@@ -56,6 +58,8 @@
@Override
public void onReceive(Context context, Intent intent) {
final Bundle extras = intent.getExtras();
+ Log.i(TAG, "onReceive: extras=" + extras);
+
if (extras == null) {
return;
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
similarity index 77%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
rename to hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
index 4e3fc89..322927e 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -19,10 +19,10 @@
import android.app.Activity;
import android.graphics.Rect;
-public class LaunchTapToFinishPipActivity extends Activity {
+public class LaunchEnterPipActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
- PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
+ PipActivity.launchEnterPipActivity(this);
}
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
index 67bbb63..52e0da8 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchImeWithPipActivity.java
@@ -32,7 +32,7 @@
final Display display = wm.getDefaultDisplay();
final Point displaySize = new Point();
display.getRealSize(displaySize);
- PipActivity.launchActivity(this, new Rect(0, displaySize.y - 150, 150, displaySize.y),
- false /* tapToLaunch */);
+ PipActivity.launchActivityIntoPinnedStack(this,
+ new Rect(0, displaySize.y - 150, 150, displaySize.y));
}
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
new file mode 100644
index 0000000..6c47fb7
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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 android.server.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that finishes itself using "moveTaskToBack".
+ */
+public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
+
+ private String mFinishPoint;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final Intent intent = getIntent();
+ mFinishPoint = intent.getExtras().getString("finish_point");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (mFinishPoint.equals("on_pause")) {
+ moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (mFinishPoint.equals("on_stop")) {
+ moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
index 8a49ed6..4d2f24f 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
@@ -30,6 +30,8 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
import android.view.WindowManager;
import java.util.Collections;
@@ -57,6 +59,13 @@
private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
// Shows this activity over the keyguard
private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+ // Adds an assertion that we do not ever get onStop() before we enter picture in picture
+ private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+ // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
+ // is processed)
+ private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+ private boolean mEnteredPictureInPicture;
private Handler mHandler = new Handler();
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -137,6 +146,11 @@
super.onPause();
unregisterReceiver(mReceiver);
+ // Pause if requested
+ if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
+ SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
+ }
+
// Enter PIP on move to background
if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
enterPictureInPictureMode();
@@ -144,9 +158,24 @@
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
+ Log.w("PipActivity", "Unexpected onStop() called before entering picture-in-picture");
+ finish();
+ }
+ }
+
+ @Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ // Mark that we've entered picture-in-picture so that we can stop checking for
+ // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
+ if (isInPictureInPictureMode) {
+ mEnteredPictureInPicture = true;
+ }
+
if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
// This call to re-enter PIP can happen too quickly (host side tests can have difficulty
// checking that the stacks ever changed). Therefor, we need to delay here slightly to
@@ -158,16 +187,27 @@
}
/**
- * Launches a new instance of the PipActivity.
+ * Launches a new instance of the PipActivity directly into the pinned stack.
*/
- static void launchActivity(Activity caller, Rect bounds, boolean tapToFinish) {
+ static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
final Intent intent = new Intent(caller, PipActivity.class);
intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(EXTRA_TAP_TO_FINISH, tapToFinish);
+ intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchBounds(bounds);
options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
caller.startActivity(intent, options.toBundle());
}
+
+ /**
+ * Launches a new instance of the PipActivity that will automatically enter PiP.
+ */
+ static void launchEnterPipActivity(Activity caller) {
+ final Intent intent = new Intent(caller, PipActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_ENTER_PIP, "true");
+ intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+ caller.startActivity(intent);
+ }
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
similarity index 63%
copy from hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
copy to hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
index 4e3fc89..578d7e5 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchTapToFinishPipActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -11,18 +11,13 @@
* 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
+ * limitations under the License.
*/
package android.server.cts;
import android.app.Activity;
-import android.graphics.Rect;
-public class LaunchTapToFinishPipActivity extends Activity {
- @Override
- protected void onResume() {
- super.onResume();
- PipActivity.launchActivity(this, new Rect(0, 0, 500, 500), true /* tapToLaunch */);
- }
+public class ResumeWhilePausingActivity extends Activity {
+ // Empty
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
index e84d5d4..68c34bf 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
@@ -27,13 +27,17 @@
import android.net.Uri;
import android.os.Bundle;
import android.server.cts.TestActivity;
+import android.util.Log;
/** Utility class which contains common code for launching activities. */
public class ActivityLauncher {
+ private static final String TAG = ActivityLauncher.class.getSimpleName();
+
public static void launchActivityFromExtras(final Context context, Bundle extras) {
if (extras == null) {
extras = new Bundle();
}
+ Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
final Intent newIntent = new Intent();
final String targetActivity = extras.getString("target_activity");
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
index 0d5f61a..a7d4042 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
@@ -28,7 +28,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityAndWindowManagerOverrideConfigTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityAndWindowManagerOverrideConfigTests
*/
public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
private static final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
index 6023039..c766caa 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
@@ -16,12 +16,14 @@
package android.server.cts;
+import static android.server.cts.ActivityManagerState.STATE_RESUMED;
+
import java.lang.Exception;
import java.lang.String;
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerActivityVisibilityTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerActivityVisibilityTests
*/
public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
private static final String TRANSLUCENT_ACTIVITY = "AlwaysFocusablePipActivity";
@@ -31,6 +33,7 @@
private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
+ private static final String MOVE_TASK_TO_BACK_ACTIVITY_NAME = "MoveTaskToBackActivity";
public void testVisibleBehindHomeActivity() throws Exception {
launchActivity(VISIBLE_BEHIND_ACTIVITY);
@@ -161,9 +164,41 @@
mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
// Finish activity in non-focused (docked) stack.
- executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
}
+
+ public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+ performFinishActivityWithMoveTaskToBack("on_pause");
+ }
+
+ public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+ performFinishActivityWithMoveTaskToBack("on_stop");
+ }
+
+ private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+ // Make sure home activity is visible.
+ launchHomeActivity();
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+
+ // Launch an activity that calls "moveTaskToBack" to finish itself.
+ launchActivity(MOVE_TASK_TO_BACK_ACTIVITY_NAME, "finish_point", finishPoint);
+ mAmWmState.waitForValidState(mDevice, MOVE_TASK_TO_BACK_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, true);
+
+ // Launch a different activity on top.
+ launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+ mAmWmState.waitForActivityState(mDevice, BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
+ mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, false);
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+
+ // Finish the top-most activity.
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ // Home must be visible.
+ mAmWmState.waitForHomeActivityVisible(mDevice);
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
index 0f77868..b31fb16 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
@@ -23,7 +23,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAmStartOptionsTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmStartOptionsTests
*/
public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index 8636415..925994a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -21,7 +21,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAppConfigurationTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAppConfigurationTests
*/
public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
index 45630e4..ef296ad 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -18,7 +18,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerConfigChangeTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
*/
public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
index 09b4edf..09bbb02 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
@@ -32,7 +32,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerDisplayTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayTests
*/
public class ActivityManagerDisplayTests extends ActivityManagerTestBase {
private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
@@ -314,7 +314,7 @@
sleepDevice();
// Finish activity on secondary display.
- executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
// Unlock and check if the focus is switched back to primary display.
wakeUpAndUnlockDevice();
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index 74741d9..ec59164 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -20,7 +20,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerDockedStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDockedStackTests
*/
public class ActivityManagerDockedStackTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
index b7dff24..a3d58f2 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
@@ -21,7 +21,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerFreeformStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerFreeformStackTests
*/
public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
index a4dfcee..b7df926 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
@@ -34,7 +34,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerManifestLayoutTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerManifestLayoutTests
*/
public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
index e6be28c..e2c8534 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
@@ -31,26 +31,33 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerPinnedStackTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests
*/
public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
private static final String TEST_ACTIVITY = "TestActivity";
private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
+ private static final String RESUME_WHILE_PAUSING_ACTIVITY = "ResumeWhilePausingActivity";
private static final String PIP_ACTIVITY = "PipActivity";
private static final String ALWAYS_FOCUSABLE_PIP_ACTIVITY = "AlwaysFocusablePipActivity";
private static final String LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
"LaunchIntoPinnedStackPipActivity";
- private static final String LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY = "LaunchTapToFinishPipActivity";
private static final String LAUNCH_IME_WITH_PIP_ACTIVITY = "LaunchImeWithPipActivity";
+ private static final String LAUNCHER_ENTER_PIP_ACTIVITY = "LaunchEnterPipActivity";
private static final String PIP_ON_STOP_ACTIVITY = "PipOnStopActivity";
private static final String EXTRA_ENTER_PIP = "enter_pip";
private static final String EXTRA_ENTER_PIP_ASPECT_RATIO = "enter_pip_aspect_ratio";
private static final String EXTRA_SET_ASPECT_RATIO = "set_aspect_ratio";
private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+ private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
private static final String EXTRA_START_ACTIVITY = "start_activity";
private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+ private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+ private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+ private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
+ "android.server.cts.PipActivity.enter_pip";
private static final int APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
private static final int APP_OPS_MODE_ALLOWED = 0;
@@ -87,8 +94,12 @@
}
public void testNonTappablePipActivity() throws Exception {
+ if (!supportsPip()) return;
+
// Launch the tap-to-finish activity at a specific place
- launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_TAP_TO_FINISH, "true");
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
assertPinnedStackExists();
@@ -101,6 +112,8 @@
}
public void testPinnedStackDefaultBounds() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a PIP activity
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
@@ -123,6 +136,8 @@
}
public void testPinnedStackMovementBounds() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a PIP activity
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
@@ -145,10 +160,14 @@
}
public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
+ if (!supportsPip()) return;
+
final WindowManagerState wmState = mAmWmState.getWmState();
// Launch an activity into the pinned stack
- launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_TAP_TO_FINISH, "true");
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
// Get the display dimensions
@@ -169,8 +188,12 @@
}
public void testPinnedStackInBoundsAfterRotation() throws Exception {
+ if (!supportsPip()) return;
+
// Launch an activity into the pinned stack
- launchActivity(LAUNCH_TAP_TO_FINISH_PIP_ACTIVITY);
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_TAP_TO_FINISH, "true");
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
// Ensure that the PIP stack is fully visible in each orientation
@@ -186,6 +209,8 @@
}
public void testPinnedStackOffsetForIME() throws Exception {
+ if (!supportsPip()) return;
+
// Launch an activity which shows an IME
launchActivity(LAUNCH_IME_WITH_PIP_ACTIVITY);
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -202,6 +227,8 @@
}
public void testEnterPipAspectRatio() throws Exception {
+ if (!supportsPip()) return;
+
launchActivity(PIP_ACTIVITY,
EXTRA_ENTER_PIP, "true",
EXTRA_ENTER_PIP_ASPECT_RATIO, Float.toString(VALID_ASPECT_RATIO));
@@ -215,6 +242,8 @@
}
public void testResizePipAspectRatio() throws Exception {
+ if (!supportsPip()) return;
+
launchActivity(PIP_ACTIVITY,
EXTRA_ENTER_PIP, "true",
EXTRA_SET_ASPECT_RATIO, Float.toString(VALID_ASPECT_RATIO));
@@ -234,6 +263,8 @@
}
public void testEnterPipExtremeAspectRatios() throws Exception {
+ if (!supportsPip()) return;
+
// Assert that we could not create a pinned stack with an extreme aspect ratio
launchActivity(PIP_ACTIVITY,
EXTRA_ENTER_PIP, "true",
@@ -242,6 +273,8 @@
}
public void testSetPipExtremeAspectRatios() throws Exception {
+ if (!supportsPip()) return;
+
// Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
// fails (the aspect ratio remains the same)
launchActivity(PIP_ACTIVITY,
@@ -256,6 +289,8 @@
}
public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
+ if (!supportsPip()) return;
+
// Launch the bottom pip activity
launchActivity(PIP_ON_STOP_ACTIVITY);
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -268,6 +303,8 @@
}
public void testAutoEnterPictureInPicture() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a test activity so that we're not over home
launchActivity(TEST_ACTIVITY);
@@ -281,6 +318,8 @@
}
public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a test activity so that we're not over home
launchActivity(TEST_ACTIVITY);
@@ -300,6 +339,8 @@
}
public void testAutoEnterPictureInPictureFinish() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a test activity so that we're not over home
launchActivity(TEST_ACTIVITY);
@@ -313,6 +354,8 @@
}
public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
+ if (!supportsPip()) return;
+
// Launch the PIP activity on pause, and set the aspect ratio
launchActivity(PIP_ACTIVITY,
EXTRA_ENTER_PIP_ON_PAUSE, "true",
@@ -336,6 +379,8 @@
}
public void testAutoEnterPictureInPictureOverPip() throws Exception {
+ if (!supportsPip()) return;
+
// Launch another PIP activity
launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
@@ -357,6 +402,8 @@
}
public void testPipUnPipOverHome() throws Exception {
+ if (!supportsPip()) return;
+
// Go home
launchHomeActivity();
// Launch an auto pip activity
@@ -365,11 +412,8 @@
EXTRA_REENTER_PIP_ON_EXIT, "true");
assertPinnedStackExists();
- // Tap the screen at a known location in the pinned stack bounds to trigger the activity
- // to exit and re-enter pip
- // TODO: add channel for expanding the PIP, but for now, just force-tap twice
- tapToFinishPip();
- tapToFinishPip();
+ // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+ launchActivity(PIP_ACTIVITY);
mAmWmState.waitForWithAmState(mDevice, (amState) -> {
return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
}, "Waiting for PIP to exit to fullscreen");
@@ -380,6 +424,8 @@
}
public void testPipUnPipOverApp() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a test activity so that we're not over home
launchActivity(TEST_ACTIVITY);
@@ -389,11 +435,8 @@
EXTRA_REENTER_PIP_ON_EXIT, "true");
assertPinnedStackExists();
- // Tap the screen at a known location in the pinned stack bounds to trigger the activity
- // to exit and re-enter pip
- // TODO: add channel for expanding the PIP, but for now, just force-tap twice
- tapToFinishPip();
- tapToFinishPip();
+ // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+ launchActivity(PIP_ACTIVITY);
mAmWmState.waitForWithAmState(mDevice, (amState) -> {
return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
}, "Waiting for PIP to exit to fullscreen");
@@ -405,6 +448,8 @@
}
public void testRemovePipWithNoFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
// Start with a clean slate, remove all the stacks but home
removeStacks(ALL_STACK_IDS_BUT_HOME);
@@ -425,6 +470,8 @@
}
public void testRemovePipWithVisibleFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a fullscreen activity, and a pip activity over that
launchActivity(TEST_ACTIVITY);
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
@@ -447,6 +494,8 @@
}
public void testRemovePipWithHiddenFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
// Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
// launch a pip activity over home
launchActivity(TEST_ACTIVITY);
@@ -470,6 +519,8 @@
}
public void testPinnedStackAlwaysOnTop() throws Exception {
+ if (!supportsPip()) return;
+
// Launch activity into pinned stack and assert it's on top.
launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
assertPinnedStackExists();
@@ -487,6 +538,8 @@
}
public void testAppOpsDenyPipOnPause() throws Exception {
+ if (!supportsPip()) return;
+
// Disable enter-pip-on-hide and try to enter pip
setAppOpsOpToMode(ActivityManagerTestBase.componentName,
APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_IGNORED);
@@ -504,6 +557,64 @@
APP_OPS_OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE, APP_OPS_MODE_ALLOWED);
}
+ public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
+ if (!supportsPip()) return;
+
+ // Try to enter picture-in-picture from an activity that has more than one activity in the
+ // task and ensure that it works
+ launchActivity(LAUNCHER_ENTER_PIP_ACTIVITY);
+ mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
+ assertPinnedStackExists();
+ }
+
+ public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
+ if (!supportsPip()) return;
+
+ /*
+ * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
+ * stopped and actually went into the pinned stack.
+ *
+ * Note that this is a workaround because to trigger the path that we want to happen in
+ * activity manager, we need to add the leaving activity to the stopping state, which only
+ * happens when a hidden stack is brought forward. Normally, this happens when you go home,
+ * but since we can't launch into the home stack directly, we have a workaround.
+ *
+ * 1) Launch an activity in a new dynamic stack
+ * 2) Resize the dynamic stack to non-fullscreen bounds
+ * 3) Start the PiP activity that will enter picture-in-picture when paused in the
+ * fullscreen stack
+ * 4) Bring the activity in the dynamic stack forward to trigger PiP
+ */
+ int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
+ resizeStack(stackId, 0, 0, 500, 500);
+ // Launch an activity that will enter PiP when it is paused with a delay that is long enough
+ // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
+ // trigger the current system pause timeout (currently 500ms)
+ launchActivityInStack(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
+ EXTRA_ENTER_PIP_ON_PAUSE, "true",
+ EXTRA_ON_PAUSE_DELAY, "350",
+ EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+ launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+ assertPinnedStackExists();
+ }
+
+ public void testDisallowEnterPipActivityLocked() throws Exception {
+ if (!supportsPip()) return;
+
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+ ActivityTask task =
+ mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
+
+ // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
+ // when paused
+ executeShellCommand("am task lock " + task.mTaskId);
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ assertPinnedStackDoesNotExist();
+ launchHomeActivity();
+ assertPinnedStackDoesNotExist();
+ executeShellCommand("am task lock stop");
+ }
+
/**
* Asserts that the pinned stack bounds does not intersect with the IME bounds.
*/
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
index 3c1ba76..59d632a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
@@ -31,7 +31,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerReplaceWindowTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerReplaceWindowTests
*/
public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
index 7e79a85..7b366d4 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
@@ -36,7 +36,7 @@
* The exact animation is unspecified and can be overridden.
*
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerTransitionSelectionTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerTransitionSelectionTests
*/
public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
index 1179b12..456225a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
@@ -18,7 +18,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.AnimationBackgroundTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.AnimationBackgroundTests
*/
public class AnimationBackgroundTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
index 771eab2..0ebbfe2 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
@@ -25,7 +25,7 @@
* an unsupported smallest width.
*
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.DisplaySizeTest
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.DisplaySizeTest
*/
public class DisplaySizeTest extends DeviceTestCase {
private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
index c30990d..a445b54 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
@@ -18,7 +18,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardLockedTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardLockedTests
*/
public class KeyguardLockedTests extends KeyguardTestBase {
@@ -123,7 +123,7 @@
}
public void testEnterPipOverKeyguard() throws Exception {
- if (!isHandheld()) {
+ if (!isHandheld() || !supportsPip()) {
return;
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
index 093639a..0128e2c 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
@@ -20,7 +20,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTests
*/
public class KeyguardTests extends KeyguardTestBase {
@@ -256,7 +256,7 @@
launchActivity("KeyguardLockActivity");
mAmWmState.computeState(mDevice, new String[] { "KeyguardLockActivity" });
mAmWmState.assertVisibility("KeyguardLockActivity", true);
- executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
assertShowingAndNotOccluded();
}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
index 4aa663a..e67cdd9 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
@@ -24,7 +24,7 @@
/**
* Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.KeyguardTransitionTests
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTransitionTests
*/
public class KeyguardTransitionTests extends ActivityManagerTestBase {
diff --git a/hostsidetests/services/activityandwindowmanager/util/run-test b/hostsidetests/services/activityandwindowmanager/util/run-test
index 131fbc5..46cdc44 100755
--- a/hostsidetests/services/activityandwindowmanager/util/run-test
+++ b/hostsidetests/services/activityandwindowmanager/util/run-test
@@ -2,11 +2,11 @@
# Helper script for running CTS tests with all the right params.
# Example usage:
-# run-test <package_name> // To run all the tests in a package.
-# run-test <package_name>.<class_name> // To run all the tests in a class.
-# run-test <package_name>.<class_name>#<method_name> // To run a specific test in a class.
+# run-test <module> <package_name> // To run all the tests in a package.
+# run-test <module> <package_name>.<class_name> // To run all the tests in a class.
+# run-test <module> <package_name>.<class_name>#<method_name> // To run a specific test in a class.
echo " "
-echo "Running test...$1"
+echo "Running module $1 test...$2"
echo " "
-cts-tradefed run commandAndExit cts-dev --module CtsServicesHostTestCases --test $1 --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
+cts-tradefed run commandAndExit cts-dev --module $1 --test $2 --disable-reboot --skip-device-info --skip-all-system-status-check --skip-preconditions
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
index 89d554f..a1d7553 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
@@ -27,18 +27,24 @@
import java.lang.Integer;
import java.lang.String;
import java.util.HashSet;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static android.server.cts.StateLogger.log;
import static android.server.cts.StateLogger.logE;
+import android.server.cts.ActivityManagerState.ActivityStack;
+
public abstract class ActivityManagerTestBase extends DeviceTestCase {
private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
// Constants copied from ActivityManager.StackId. If they are changed there, these must be
// updated.
+ /** Invalid stack ID. */
+ public static final int INVALID_STACK_ID = -1;
+
/** First static stack ID. */
public static final int FIRST_STATIC_STACK_ID = 0;
@@ -87,7 +93,12 @@
static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
+ /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+ static final String FINISH_ACTIVITY_BROADCAST
+ = "am broadcast -a trigger_broadcast --ez finish true";
+
private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
+ private static final String AM_RESIZE_STACK = "am stack resize ";
static final String AM_MOVE_TASK = "am stack move-task ";
@@ -253,6 +264,37 @@
mAmWmState.waitForValidState(mDevice, targetActivityName);
}
+ /**
+ * Starts an activity in a new stack.
+ * @return the stack id of the newly created stack.
+ */
+ protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
+ HashSet<Integer> stackIds = getStackIds();
+ executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
+ + " " + getActivityComponentName(activityName));
+ HashSet<Integer> newStackIds = getStackIds();
+ newStackIds.removeAll(stackIds);
+ if (newStackIds.isEmpty()) {
+ return INVALID_STACK_ID;
+ } else {
+ assertTrue(newStackIds.size() == 1);
+ return newStackIds.iterator().next();
+ }
+ }
+
+ /**
+ * Returns the set of stack ids.
+ */
+ private HashSet<Integer> getStackIds() throws Exception {
+ mAmWmState.computeState(mDevice, null);
+ final List<ActivityStack> stacks = mAmWmState.getAmState().getStacks();
+ final HashSet<Integer> stackIds = new HashSet<>();
+ for (ActivityStack s : stacks) {
+ stackIds.add(s.mStackId);
+ }
+ return stackIds;
+ }
+
protected void launchHomeActivity()
throws Exception {
executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
@@ -308,8 +350,9 @@
mAmWmState.waitForValidState(mDevice, targetActivityName);
}
- protected void launchActivityInStack(String activityName, int stackId) throws Exception {
- executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
+ protected void launchActivityInStack(String activityName, int stackId,
+ final String... keyValuePairs) throws Exception {
+ executeShellCommand(getAmStartCmd(activityName, keyValuePairs) + " --stack " + stackId);
mAmWmState.waitForValidState(mDevice, activityName, stackId);
}
@@ -358,6 +401,12 @@
+ " 0 0 " + taskWidth + " " + taskHeight);
}
+ protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
+ int stackHeight) throws DeviceNotAvailableException {
+ executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
+ stackTop, stackWidth, stackHeight));
+ }
+
protected void pressHomeButton() throws DeviceNotAvailableException {
executeShellCommand(INPUT_KEYEVENT_HOME);
}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
index 63d0503..878f081 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
@@ -27,6 +27,7 @@
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -341,8 +342,7 @@
}
}
- public void getMatchingVisibleWindowState(final String windowName,
- List<WindowState> windowList) {
+ void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
windowList.clear();
for (WindowState ws : mWindowStates) {
if (ws.isShown() && windowName.equals(ws.getName())) {
@@ -351,6 +351,40 @@
}
}
+ WindowState getWindowByPackageName(String packageName, int windowType) {
+ for (WindowState ws : mWindowStates) {
+ final String name = ws.getName();
+ if (name == null || !name.contains(packageName)) {
+ continue;
+ }
+ if (windowType != ws.getType()) {
+ continue;
+ }
+ return ws;
+ }
+
+ return null;
+ }
+
+ void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
+ List<WindowState> outWindowList) {
+ outWindowList.clear();
+ for (WindowState ws : mWindowStates) {
+ final String name = ws.getName();
+ if (name == null || !name.contains(packageName)) {
+ continue;
+ }
+ if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
+ continue;
+ }
+ outWindowList.add(ws);
+ }
+ }
+
+ void sortWindowsByLayer(List<WindowState> windows) {
+ windows.sort(Comparator.comparingInt(WindowState::getLayer));
+ }
+
WindowState getWindowStateForAppToken(String appToken) {
for (WindowState ws : mWindowStates) {
if (ws.getToken().equals(appToken)) {
@@ -409,10 +443,13 @@
return false;
}
+ /** Check if at least one window which matches provided window name is visible. */
boolean isWindowVisible(String windowName) {
for (WindowState window : mWindowStates) {
if (window.getName().equals(windowName)) {
- return window.isShown();
+ if (window.isShown()) {
+ return true;
+ }
}
}
return false;
@@ -1043,7 +1080,7 @@
@Override
public String toString() {
return "WindowState: {" + mAppToken + " " + mName
- + getWindowTypeSuffix(mWindowType) + "}"
+ + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
+ " cf=" + mContainingFrame + " pf=" + mParentFrame;
}
}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
index 67c4879..f1d8397 100644
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
@@ -13,13 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Config for CTS drag and drop host test cases">
+<configuration description="Config for CTS window manager host test cases">
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsDragAndDropSourceApp.apk" />
<option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
<option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk" />
<option name="test-file-name" value="CtsDeviceWindowFramesTestApp.apk" />
+ <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk" />
+ <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsWindowManagerHostTestCases.jar" />
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
new file mode 100644
index 0000000..a0f71dd
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../alertwindowappsdk25/src) \
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
new file mode 100755
index 0000000..5534e7e
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.server.alertwindowapp">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <activity android:name=".AlertWindowTestActivity"
+ android:windowSoftInputMode="stateAlwaysVisible"
+ android:exported="true"
+ />
+ </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
new file mode 100644
index 0000000..db007e2
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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 android.server.alertwindowapp;
+
+import android.os.Bundle;
+import android.server.alertwindowappsdk25.AlertWindowTestBaseActivity;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
+ private static final int[] ALERT_WINDOW_TYPES = {
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY,
+ TYPE_APPLICATION_OVERLAY
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ createAllAlertWindows(getPackageName());
+ }
+
+ @Override
+ protected int[] getAlertWindowTypes() {
+ return ALERT_WINDOW_TYPES;
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
new file mode 100644
index 0000000..940f7a6
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := 25
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsDeviceAlertWindowTestAppSdk25
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
new file mode 100755
index 0000000..7b08dae
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.alertwindowappsdk25">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <activity android:name=".AlertWindowTestActivitySdk25"
+ android:windowSoftInputMode="stateAlwaysVisible"
+ android:exported="true"
+ />
+ </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
new file mode 100644
index 0000000..046879f
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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 android.server.alertwindowappsdk25;
+
+import android.os.Bundle;
+
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
+ private static final int[] ALERT_WINDOW_TYPES = {
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ createAllAlertWindows(getPackageName());
+ }
+
+ @Override
+ protected int[] getAlertWindowTypes() {
+ return ALERT_WINDOW_TYPES;
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
new file mode 100644
index 0000000..2d0dad0
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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 android.server.alertwindowappsdk25;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public abstract class AlertWindowTestBaseActivity extends Activity {
+
+ protected void createAllAlertWindows(String windowName) {
+ final int[] alertWindowTypes = getAlertWindowTypes();
+ for (int type : alertWindowTypes) {
+ try {
+ createAlertWindow(type, windowName);
+ } catch (Exception e) {
+ Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
+ }
+ }
+ }
+
+ protected void createAlertWindow(int type) {
+ createAlertWindow(type, getPackageName());
+ }
+
+ protected void createAlertWindow(int type, String windowName) {
+ if (!isSystemAlertWindowType(type)) {
+ throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
+ }
+
+ final Point size = new Point();
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getSize(size);
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+ params.width = size.x / 3;
+ params.height = size.y / 3;
+ params.gravity = TOP | LEFT;
+ params.setTitle(windowName);
+
+ final TextView view = new TextView(this);
+ view.setText(windowName + " type=" + type);
+ view.setBackgroundColor(Color.RED);
+ wm.addView(view, params);
+ }
+
+ private boolean isSystemAlertWindowType(int type) {
+ final int[] alertWindowTypes = getAlertWindowTypes();
+ for (int current : alertWindowTypes) {
+ if (current == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected abstract int[] getAlertWindowTypes();
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
new file mode 100644
index 0000000..84eabf2
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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 android.server.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsWindowManagerHostTestCases android.server.cts.AlertWindowsTests
+ */
+public class AlertWindowsTests extends ActivityManagerTestBase {
+
+ private static final String PACKAGE_NAME = "android.server.alertwindowapp";
+ private static final String ACTIVITY_NAME = "AlertWindowTestActivity";
+ private static final String SDK_25_PACKAGE_NAME = "android.server.alertwindowappsdk25";
+ private static final String SDK_25_ACTIVITY_NAME = "AlertWindowTestActivitySdk25";
+
+ // From WindowManager.java
+ private static final int TYPE_BASE_APPLICATION = 1;
+ private static final int FIRST_SYSTEM_WINDOW = 2000;
+
+ private static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2;
+ private static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3;
+ private static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6;
+ private static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7;
+ private static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10;
+ private static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
+
+ private static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
+ private static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11;
+ private static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW + 19;
+
+ private final List<Integer> mAlertWindowTypes = Arrays.asList(
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY,
+ TYPE_APPLICATION_OVERLAY);
+ private final List<Integer> mSystemWindowTypes = Arrays.asList(
+ TYPE_STATUS_BAR,
+ TYPE_INPUT_METHOD,
+ TYPE_NAVIGATION_BAR);
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ try {
+ setAlertWindowPermission(PACKAGE_NAME, false);
+ setAlertWindowPermission(SDK_25_PACKAGE_NAME, false);
+ executeShellCommand("am force-stop " + PACKAGE_NAME);
+ executeShellCommand("am force-stop " + SDK_25_PACKAGE_NAME);
+ } catch (DeviceNotAvailableException e) {
+ }
+ }
+
+ public void testAlertWindowAllowed() throws Exception {
+ runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, true /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ public void testAlertWindowDisallowed() throws Exception {
+ runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, false /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ public void testAlertWindowAllowedSdk25() throws Exception {
+ runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
+ true /* hasAlertWindowPermission */, false /* atLeastO */);
+ }
+
+ public void testAlertWindowDisallowedSdk25() throws Exception {
+ runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
+ false /* hasAlertWindowPermission */, false /* atLeastO */);
+ }
+
+ private void runAlertWindowTest(String packageName, String activityName,
+ boolean hasAlertWindowPermission, boolean atLeastO) throws Exception {
+ setComponentName(packageName);
+ setAlertWindowPermission(packageName, hasAlertWindowPermission);
+
+ executeShellCommand(getAmStartCmd(activityName));
+ mAmWmState.computeState(mDevice, new String[] { activityName });
+ mAmWmState.assertVisibility(activityName, true);
+
+ assertAlertWindows(packageName, hasAlertWindowPermission, atLeastO);
+ }
+
+ private void assertAlertWindows(String packageName, boolean hasAlertWindowPermission,
+ boolean atLeastO) {
+ final WindowManagerState wMState = mAmWmState.getWmState();
+
+ final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList();
+ wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
+
+ if (!hasAlertWindowPermission) {
+ assertTrue("Should be empty alertWindows=" + alertWindows, alertWindows.isEmpty());
+ return;
+ }
+
+ if (atLeastO) {
+ // Assert that only TYPE_APPLICATION_OVERLAY was created.
+ for (WindowManagerState.WindowState win : alertWindows) {
+ assertTrue("Can't create win=" + win + " on SDK O or greater",
+ win.getType() == TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ final WindowManagerState.WindowState mainAppWindow =
+ wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
+
+ assertNotNull(mainAppWindow);
+
+ wMState.sortWindowsByLayer(alertWindows);
+ final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
+ final WindowManagerState.WindowState highestAlertWindow =
+ alertWindows.get(alertWindows.size() - 1);
+
+ // Assert that the alert windows have higher z-order than the main app window
+ assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
+ + mainAppWindow, lowestAlertWindow.getLayer() > mainAppWindow.getLayer());
+
+ // Assert that legacy alert windows have a lower z-order than the new alert window layer.
+ final WindowManagerState.WindowState appOverlayWindow =
+ wMState.getWindowByPackageName(packageName, TYPE_APPLICATION_OVERLAY);
+ if (appOverlayWindow != null && highestAlertWindow != appOverlayWindow) {
+ assertTrue("highestAlertWindow=" + highestAlertWindow
+ + " greater than appOverlayWindow=" + appOverlayWindow,
+ highestAlertWindow.getLayer() < appOverlayWindow.getLayer());
+ }
+
+ // Assert that alert windows are below key system windows.
+ final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList();
+ wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
+ if (!systemWindows.isEmpty()) {
+ wMState.sortWindowsByLayer(systemWindows);
+ final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
+ assertTrue("highestAlertWindow=" + highestAlertWindow
+ + " greater than lowestSystemWindow=" + lowestSystemWindow,
+ highestAlertWindow.getLayer() < lowestSystemWindow.getLayer());
+ }
+ }
+
+ private void setAlertWindowPermission(String packageName, boolean allow) throws Exception {
+ executeShellCommand("appops set " + packageName + " android:system_alert_window "
+ + (allow ? "allow" : "deny"));
+ }
+}
diff --git a/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java b/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
index fffeeb8..702b15bb 100644
--- a/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
+++ b/hostsidetests/sustainedperf/src/android/SustainedPerformance/cts/SustainedPerformanceHostTest.java
@@ -219,12 +219,21 @@
device.executeShellCommand("settings put global airplane_mode_on 0");
device.executeShellCommand("am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false");
+ double resDhry = dhrystoneResultsWithMode.get(2);
+ double resApp = appResultsWithMode.get(2);
+
+ /* Report if performance is below 5% margin for both dhrystone and shader */
+ if ((resDhry > 5) || (resApp > 5)) {
+ Log.w("SustainedPerformanceHostTests",
+ "Sustainable mode results, Dhrystone: " + resDhry + " App: " + resApp);
+ }
+
/*
- * Checks if the performance in the mode is consistent with
+ * Error if the performance in the mode is not consistent with
* 5% error margin for shader and 10% error margin for dhrystone.
*/
assertFalse("Results in the mode are not sustainable",
- (dhrystoneResultsWithMode.get(2) > 10) ||
- (appResultsWithMode.get(2)) > 5);
+ (resDhry > 15) ||
+ (resApp > 5));
}
}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 413ff7d..1dfbf94 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -28,6 +28,7 @@
import android.view.accessibility.cts.R;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -36,9 +37,12 @@
public class AccessibilityNodeInfoTest extends AndroidTestCase {
/** The number of properties of the {@link AccessibilityNodeInfo} class that are marshalled. */
- private static final int NUM_MARSHALLED_PROPERTIES = 32;
+ private static final int NUM_MARSHALLED_PROPERTIES = 33;
- /** The number of properties that are purposely not marshalled */
+ /**
+ * The number of properties that are purposely not marshalled
+ * mOriginalText - Used when resolving clickable spans; intentionally not parceled
+ */
private static final int NUM_NONMARSHALLED_PROPERTIES = 1;
@SmallTest
@@ -227,6 +231,8 @@
info.setLabelFor(new View(getContext()));
info.setViewIdResourceName("foo.bar:id/baz");
info.setDrawingOrder(5);
+ info.setAvailableExtraData(
+ Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
}
/**
@@ -296,6 +302,8 @@
receivedInfo.getViewIdResourceName());
assertEquals("drawing order has incorrect value", expectedInfo.getDrawingOrder(),
receivedInfo.getDrawingOrder());
+ assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
+ receivedInfo.getAvailableExtraData());
}
/**
@@ -333,5 +341,6 @@
info.getMovementGranularities());
assertNull("viewId not properly recycled", info.getViewIdResourceName());
assertEquals(0, info.getDrawingOrder());
+ assertTrue(info.getAvailableExtraData().isEmpty());
}
}
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index e13cdee..f996c8b 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application android:theme="@android:style/Theme.Holo.NoActionBar">
@@ -95,6 +96,32 @@
android:resource="@xml/stub_magnification_a11y_service" />
</service>
+ <service
+ android:name=".StubFingerprintGestureService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/stub_fingerprint_gesture_service" />
+ </service>
+
+ <service
+ android:name=".StubAccessibilityButtonService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/stub_accessibility_button_service" />
+ </service>
+
</application>
<instrumentation
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 7c9547c..3a6b855 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -141,6 +141,10 @@
<string name="stub_gesture_a11y_service_description">com.android.accessibilityservice.cts.StubGestureAccessibilityService</string>
+ <string name="stub_fprint_a11y_service_description">com.android.accessibilityservice.cts.StubFingerprintGestureAccessibilityService</string>
+
+ <string name="stub_accessibility_button_service_description">com.android.accessibilityservice.cts.StubAccessibilityButtonService</string>
+
<string name="android_wiki_paragraphs">
\n\nAndroid is a\n\n It is developed\n The
unveiling\n\n</string>
diff --git a/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml b/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml
new file mode 100644
index 0000000..cbf6e7e
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_accessibility_button_service.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/stub_accessibility_button_service_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagRequestAccessibilityButton"
+ android:notificationTimeout="0" />
\ No newline at end of file
diff --git a/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml b/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml
new file mode 100644
index 0000000..c88b8cd
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_fingerprint_gesture_service.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/stub_fprint_a11y_service_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagCaptureFingerprintGestures"
+ android:canCaptureFingerprintGestures="true"
+ android:notificationTimeout="0" />
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
new file mode 100644
index 0000000..9a32d3d
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2017 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 android.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityButtonController;
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases for accessibility service APIs related to the accessibility button within
+ * software-rendered navigation bars.
+ *
+ * TODO: Extend coverage with a more precise signal if a device is compatible with the button
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityButtonTest {
+
+ private StubAccessibilityButtonService mService;
+ private AccessibilityButtonController mButtonController;
+ private AccessibilityButtonController.AccessibilityButtonCallback mStubCallback =
+ new AccessibilityButtonController.AccessibilityButtonCallback() {
+ @Override
+ public void onClicked(AccessibilityButtonController controller) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {
+ /* do nothing */
+ }
+ };
+
+ @Before
+ public void setUp() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mService = StubAccessibilityButtonService.enableSelf(instrumentation);
+ mButtonController = mService.getAccessibilityButtonController();
+ }
+
+ @After
+ public void tearDown() {
+ mService.runOnServiceSync(() -> mService.disableSelf());
+ }
+
+ @Test
+ public void testCallbackRegistrationUnregistration_serviceDoesNotCrash() {
+ mButtonController.registerAccessibilityButtonCallback(mStubCallback);
+ mButtonController.unregisterAccessibilityButtonCallback(mStubCallback);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 3caea71..b14b76a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -339,7 +339,7 @@
try {
// create the notification to send
channel.enableVibration(true);
- channel.setLights(true);
+ channel.enableLights(true);
channel.setBypassDnd(true);
notificationManager.createNotificationChannel(channel);
NotificationChannel created =
@@ -404,7 +404,7 @@
getInstrumentation()
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- this, InstrumentedAccessibilityService.class);
+ getInstrumentation(), InstrumentedAccessibilityService.class);
try {
assertFalse(service.wasOnInterruptCalled());
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
new file mode 100644
index 0000000..07add0b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (C) 2017 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 android.accessibilityservice.cts;
+
+import android.accessibilityservice.FingerprintGestureController;
+import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
+import android.app.Instrumentation;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Verify that a service listening for fingerprint gestures gets called back when apps
+ * use the fingerprint sensor to authenticate.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityFingerprintGestureTest {
+ private static final int FINGERPRINT_CALLBACK_TIMEOUT = 3000;
+
+ boolean mIsHardwareAvailable;
+ FingerprintManager mFingerprintManager;
+ StubFingerprintGestureService mFingerprintGestureService;
+ FingerprintGestureController mFingerprintGestureController;
+ CancellationSignal mCancellationSignal = new CancellationSignal();
+
+ @Mock FingerprintManager.AuthenticationCallback mMockAuthenticationCallback;
+ @Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mFingerprintManager = instrumentation.getContext()
+ .getSystemService(FingerprintManager.class);
+ mFingerprintGestureService = StubFingerprintGestureService.enableSelf(instrumentation);
+ mIsHardwareAvailable = (mFingerprintManager == null) ? false :
+ mFingerprintManager.isHardwareDetected();
+ mFingerprintGestureController =
+ mFingerprintGestureService.getFingerprintGestureController();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mFingerprintGestureService.runOnServiceSync(() -> mFingerprintGestureService.disableSelf());
+ }
+
+ @Test
+ public void testGetFingerprintManager_returnsManagerIfFeatureAvailable() {
+ if (!mIsHardwareAvailable) {
+ assertNull(mFingerprintGestureController);
+ return;
+ }
+ assertNotNull(mFingerprintGestureController);
+ }
+
+ @Test
+ public void testGestureDetectionAvailable_initialState_shouldBeAvailable() {
+ if (!mIsHardwareAvailable) {
+ return;
+ }
+ assertTrue(mFingerprintGestureController.isGestureDetectionAvailable());
+ }
+
+ @Test
+ public void testGestureDetectionListener_whenAuthenticationStartsAndStops_calledBack() {
+ if (!mIsHardwareAvailable) {
+ return;
+ }
+ mFingerprintGestureController.registerFingerprintGestureCallback(
+ mMockFingerprintGestureCallback, null);
+ try {
+ mFingerprintManager.authenticate(
+ null, mCancellationSignal, 0, mMockAuthenticationCallback, null);
+
+ verify(mMockFingerprintGestureCallback, timeout(FINGERPRINT_CALLBACK_TIMEOUT))
+ .onGestureDetectionAvailabilityChanged(false);
+ assertFalse(mFingerprintGestureController.isGestureDetectionAvailable());
+ reset(mMockFingerprintGestureCallback);
+ } finally {
+ mCancellationSignal.cancel();
+ }
+ verify(mMockFingerprintGestureCallback, timeout(FINGERPRINT_CALLBACK_TIMEOUT))
+ .onGestureDetectionAvailabilityChanged(true);
+ assertTrue(mFingerprintGestureController.isGestureDetectionAvailable());
+ mFingerprintGestureController.unregisterFingerprintGestureCallback(
+ mMockFingerprintGestureCallback);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index 31ccee1..23949f2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -105,7 +105,7 @@
mFullScreenTextView.setOnTouchListener(mMyTouchListener);
});
- mService = StubGestureAccessibilityService.enableSelf(this);
+ mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
mMotionEvents.clear();
mCallback = new MyGestureCallback();
@@ -313,7 +313,7 @@
centerY + CLICK_SHIFT_FROM_CENTER_Y - mViewBounds.top);
StubMagnificationAccessibilityService magnificationService =
- StubMagnificationAccessibilityService.enableSelf(this);
+ StubMagnificationAccessibilityService.enableSelf(getInstrumentation());
android.accessibilityservice.AccessibilityService.MagnificationController
magnificationController = magnificationService.getMagnificationController();
try {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index b6dcd9d..fd4ac47 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -19,6 +19,7 @@
import android.accessibilityservice.AccessibilityService.MagnificationController;
import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
import android.content.res.Resources;
import android.graphics.Region;
import android.provider.Settings;
@@ -39,16 +40,18 @@
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
private StubMagnificationAccessibilityService mService;
+ private Instrumentation mInstrumentation;
@Override
public void setUp() throws Exception {
super.setUp();
- ShellCommandBuilder.create(this)
+ ShellCommandBuilder.create(this.getInstrumentation())
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
+ mInstrumentation = getInstrumentation();
// Starting the service will force the accessibility subsystem to examine its settings, so
// it will update magnification in the process to disable it.
- mService = StubMagnificationAccessibilityService.enableSelf(this);
+ mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
}
@Override
@@ -79,7 +82,7 @@
public void testSetScaleAndCenter() {
final MagnificationController controller = mService.getMagnificationController();
- final Resources res = getInstrumentation().getTargetContext().getResources();
+ final Resources res = mInstrumentation.getTargetContext().getResources();
final DisplayMetrics metrics = res.getDisplayMetrics();
final float scale = 2.0f;
final float x = metrics.widthPixels / 4.0f;
@@ -138,7 +141,7 @@
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- this, InstrumentedAccessibilityService.class);
+ mInstrumentation, InstrumentedAccessibilityService.class);
final MagnificationController controller2 = service.getMagnificationController();
try {
assertEquals("Magnification must reset when a service dies",
@@ -159,7 +162,7 @@
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- this, InstrumentedAccessibilityService.class);
+ mInstrumentation, InstrumentedAccessibilityService.class);
try {
final MagnificationController controller = service.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
@@ -171,13 +174,13 @@
}
public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
- ShellCommandBuilder.create(this)
+ ShellCommandBuilder.create(mInstrumentation)
.putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
.run();
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- this, InstrumentedAccessibilityService.class);
+ mInstrumentation, InstrumentedAccessibilityService.class);
try {
final MagnificationController controller = service.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
@@ -185,7 +188,7 @@
+ "gestures are active", magnificationRegion.isEmpty());
} finally {
service.runOnServiceSync(() -> service.disableSelf());
- ShellCommandBuilder.create(this)
+ ShellCommandBuilder.create(mInstrumentation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 58d8355..49c209f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -86,7 +86,7 @@
getActivity();
mService = InstrumentedAccessibilityService.enableService(
- this, InstrumentedAccessibilityService.class);
+ getInstrumentation(), InstrumentedAccessibilityService.class);
mKeyboardController = mService.getSoftKeyboardController();
mUiAutomation = getInstrumentation()
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index 4ee54b1..23650a6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -15,7 +15,10 @@
package android.accessibilityservice.cts;
import android.app.UiAutomation;
+import android.graphics.RectF;
import android.os.Bundle;
+import android.os.Debug;
+import android.os.Parcelable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
@@ -28,7 +31,13 @@
import android.accessibilityservice.cts.R;
+import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.List;
+
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
/**
* Test cases for actions taken on text views.
@@ -172,6 +181,91 @@
assertOnClickCalled();
}
+
+ public void testTextLocations_textViewShouldProvideWhenRequested() {
+ final TextView textView = (TextView) getActivity().findViewById(R.id.text);
+ makeTextViewVisibleAndSetText(textView, getString(R.string.a_b));
+
+ final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByText(getString(R.string.a_b)).get(0);
+ List<String> textAvailableExtraData = text.getAvailableExtraData();
+ assertTrue("Text view should offer text location to accessibility",
+ textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+ assertNull("Text locations should not be populated by default",
+ text.getExtras().get(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+ Bundle getTextArgs = new Bundle();
+ getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
+ getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
+ text.getText().length());
+ assertTrue("Refresh failed", text.refreshWithExtraData(
+ AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+ final Parcelable[] parcelables = text.getExtras()
+ .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+ final RectF[] locations = Arrays.copyOf(parcelables, parcelables.length, RectF[].class);
+ assertEquals(text.getText().length(), locations.length);
+ // The text should all be on one line, running left to right
+ for (int i = 0; i < locations.length; i++) {
+ assertEquals(locations[0].top, locations[i].top);
+ assertEquals(locations[0].bottom, locations[i].bottom);
+ assertTrue(locations[i].right > locations[i].left);
+ if (i > 0) {
+ assertTrue(locations[i].left > locations[i-1].left);
+ }
+ }
+ }
+
+ public void testTextLocations_textOutsideOfViewBounds_locationsShouldBeNull() {
+ final EditText editText = (EditText) getActivity().findViewById(R.id.edit);
+ makeTextViewVisibleAndSetText(editText, getString(R.string.android_wiki));
+
+ final AccessibilityNodeInfo text = mUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByText(getString(R.string.android_wiki)).get(0);
+ List<String> textAvailableExtraData = text.getAvailableExtraData();
+ assertTrue("Text view should offer text location to accessibility",
+ textAvailableExtraData.contains(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+ Bundle getTextArgs = new Bundle();
+ getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, 0);
+ getTextArgs.putInt(EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH,
+ text.getText().length());
+ assertTrue("Refresh failed", text.refreshWithExtraData(
+ AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+ Parcelable[] parcelables = text.getExtras()
+ .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+ final RectF[] locationsBeforeScroll = Arrays.copyOf(
+ parcelables, parcelables.length, RectF[].class);
+ assertEquals(text.getText().length(), locationsBeforeScroll.length);
+ // The first character should be visible immediately
+ assertFalse(locationsBeforeScroll[0].isEmpty());
+ // Some of the characters should be off the screen, and thus have empty rects. Find the
+ // break point
+ int firstNullRectIndex = -1;
+ for (int i = 1; i < locationsBeforeScroll.length; i++) {
+ boolean isNull = locationsBeforeScroll[i] == null;
+ if (firstNullRectIndex < 0) {
+ if (isNull) {
+ firstNullRectIndex = i;
+ }
+ } else {
+ assertTrue(isNull);
+ }
+ }
+
+ // Scroll down one line
+ final float oneLineDownY = locationsBeforeScroll[0].bottom;
+ getInstrumentation().runOnMainSync(() -> editText.scrollTo(0, (int) oneLineDownY + 1));
+
+ assertTrue("Refresh failed", text.refreshWithExtraData(
+ AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, getTextArgs));
+ parcelables = text.getExtras()
+ .getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
+ final RectF[] locationsAfterScroll = Arrays.copyOf(
+ parcelables, parcelables.length, RectF[].class);
+ // Now the first character should be off the screen
+ assertNull(locationsAfterScroll[0]);
+ // The first character that was off the screen should now be on it
+ assertNotNull(locationsAfterScroll[firstNullRectIndex]);
+ }
+
private void onClickCallback() {
synchronized (mClickableSpanCallbackLock) {
mClickableSpanCalled.set(true);
@@ -204,12 +298,9 @@
}
private void makeTextViewVisibleAndSetText(final TextView textView, final CharSequence text) {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- textView.setVisibility(View.VISIBLE);
- textView.setText(text);
- }
+ getInstrumentation().runOnMainSync(() -> {
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(text);
});
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index f916812..7cc973f 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -580,6 +580,19 @@
// Android TV doesn't support the divider window
return;
}
+
+ // Get com.android.internal.R.bool.config_supportsSplitScreenMultiWindow
+ try {
+ if (!getInstrumentation().getContext().getResources().getBoolean(
+ Resources.getSystem().getIdentifier(
+ "config_supportsSplitScreenMultiWindow", "bool", "android"))) {
+ // Check if split screen multi window is not supported.
+ return;
+ }
+ } catch (Resources.NotFoundException e) {
+ // Do nothing, assume split screen multi window is supported.
+ }
+
// Get com.android.internal.R.bool.config_supportsMultiWindow
if (!getInstrumentation().getContext().getResources().getBoolean(
Resources.getSystem().getIdentifier("config_supportsMultiWindow", "bool",
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 31c78c5..324cf33 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -2,6 +2,7 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
@@ -111,9 +112,9 @@
}
protected static <T extends InstrumentedAccessibilityService> T enableService(
- InstrumentationTestCase testCase, Class<T> clazz) {
+ Instrumentation instrumentation, Class<T> clazz) {
final String serviceName = clazz.getSimpleName();
- final Context context = testCase.getInstrumentation().getContext();
+ final Context context = instrumentation.getContext();
final String enabledServices = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -128,7 +129,7 @@
for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
final String serviceId = serviceInfo.getId();
if (serviceId.endsWith(serviceName)) {
- ShellCommandBuilder.create(testCase)
+ ShellCommandBuilder.create(instrumentation)
.putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
.putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
@@ -136,7 +137,7 @@
final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
if (instance == null) {
- ShellCommandBuilder.create(testCase)
+ ShellCommandBuilder.create(instrumentation)
.putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServices)
.run();
@@ -173,9 +174,9 @@
return null;
}
- public static void disableAllServices(InstrumentationTestCase testCase) {
+ public static void disableAllServices(Instrumentation instrumentation) {
final Object waitLockForA11yOff = new Object();
- final Context context = testCase.getInstrumentation().getContext();
+ final Context context = instrumentation.getContext();
final AccessibilityManager manager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.addAccessibilityStateChangeListener(b -> {
@@ -184,7 +185,7 @@
}
});
- ShellCommandBuilder.create(testCase)
+ ShellCommandBuilder.create(instrumentation)
.deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
.deleteSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED)
.run();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
index 1ad55fc..823b8d6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
@@ -16,9 +16,9 @@
package android.accessibilityservice.cts;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.ParcelFileDescriptor;
-import android.test.InstrumentationTestCase;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -31,18 +31,18 @@
public class ShellCommandBuilder {
private final LinkedList<String> mCommands = new LinkedList<>();
- private final InstrumentationTestCase mTestCase;
+ private final Instrumentation mInstrumentation;
- public static ShellCommandBuilder create(InstrumentationTestCase testCase) {
- return new ShellCommandBuilder(testCase);
+ public static ShellCommandBuilder create(Instrumentation instrumentation) {
+ return new ShellCommandBuilder(instrumentation);
}
- private ShellCommandBuilder(InstrumentationTestCase testCase) {
- mTestCase = testCase;
+ private ShellCommandBuilder(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
}
public void run() {
- final UiAutomation automation = mTestCase.getInstrumentation().getUiAutomation(
+ final UiAutomation automation = mInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
for (String command : mCommands) {
execShellCommand(automation, command);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
new file mode 100644
index 0000000..ad3050a
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2017 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 android.accessibilityservice.cts;
+
+import android.app.Instrumentation;
+
+/**
+ * A stub accessibility service to install for testing accessibility button APIs
+ */
+public class StubAccessibilityButtonService extends InstrumentedAccessibilityService {
+ public static StubAccessibilityButtonService enableSelf(Instrumentation instrumentation) {
+ return InstrumentedAccessibilityService.enableService(
+ instrumentation, StubAccessibilityButtonService.class);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
new file mode 100644
index 0000000..10776c8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2017 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 android.accessibilityservice.cts;
+
+import android.app.Instrumentation;
+
+/**
+ * A stub accessibility service to install for testing gesture dispatch
+ */
+public class StubFingerprintGestureService extends InstrumentedAccessibilityService {
+ public static StubFingerprintGestureService enableSelf(Instrumentation instrumentation) {
+ return InstrumentedAccessibilityService.enableService(
+ instrumentation, StubFingerprintGestureService.class);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
index caf5ab3..e1699e8 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
@@ -15,11 +15,8 @@
package android.accessibilityservice.cts;
import android.accessibilityservice.GestureDescription;
+import android.app.Instrumentation;
import android.os.Handler;
-import android.test.InstrumentationTestCase;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.io.IOException;
/**
* A stub accessibility service to install for testing gesture dispatch
@@ -31,8 +28,8 @@
return dispatchGesture(description, callback, handler);
}
- public static StubGestureAccessibilityService enableSelf(InstrumentationTestCase test) {
+ public static StubGestureAccessibilityService enableSelf(Instrumentation instrumentation) {
return InstrumentedAccessibilityService.enableService(
- test, StubGestureAccessibilityService.class);
+ instrumentation, StubGestureAccessibilityService.class);
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
index e5e1949..669ebda 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
@@ -14,20 +14,16 @@
package android.accessibilityservice.cts;
-import android.accessibilityservice.GestureDescription;
-import android.os.Handler;
-import android.test.InstrumentationTestCase;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.io.IOException;
+import android.app.Instrumentation;
/**
* A stub accessibility service to install for testing gesture dispatch
*/
public class StubMagnificationAccessibilityService extends InstrumentedAccessibilityService {
- public static StubMagnificationAccessibilityService enableSelf(InstrumentationTestCase test) {
+ public static StubMagnificationAccessibilityService enableSelf(
+ Instrumentation instrumentation) {
return InstrumentedAccessibilityService.enableService(
- test, StubMagnificationAccessibilityService.class);
+ instrumentation, StubMagnificationAccessibilityService.class);
}
}
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 0a7216d..1e7cf69 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -27,11 +27,29 @@
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.app.stubs"
- android:label="CTS tests of android.app">
+ android:targetPackage="android.app.stubs"
+ android:label="CTS tests of android.app">
<meta-data android:name="listener"
android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
-</manifest>
+ <instrumentation android:name=".DefaultProcessInstrumentation"
+ android:targetPackage="com.android.cts.launcherapps.simpleapp">
+ </instrumentation>
+ <instrumentation android:name=".AltProcessInstrumentation"
+ android:targetPackage="com.android.cts.launcherapps.simpleapp"
+ android:targetProcess="com.android.cts.launcherapps.simpleapp:other">
+ </instrumentation>
+
+ <instrumentation android:name=".WildcardProcessInstrumentation"
+ android:targetPackage="com.android.cts.launcherapps.simpleapp"
+ android:targetProcess="*">
+ </instrumentation>
+
+ <instrumentation android:name=".MultiProcessInstrumentation"
+ android:targetPackage="com.android.cts.launcherapps.simpleapp"
+ android:targetProcess="com.android.cts.launcherapps.simpleapp:other,com.android.cts.launcherapps.simpleapp">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 6487bd7..78f5f76 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsSimpleApp.apk" />
<option name="test-file-name" value="CtsAppTestStubs.apk" />
+ <option name="test-file-name" value="CtsAppTestStubsDifferentUid.apk" />
<option name="test-file-name" value="CtsAppTestCases.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index d0f63bb..f3bb8d3 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -17,6 +17,8 @@
package android.app.stubs;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +33,7 @@
private static final String TAG = "LocalForegroundService";
private static final String EXTRA_COMMAND = "LocalForegroundService.command";
+ private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
public static final int COMMAND_START_FOREGROUND = 1;
public static final int COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION = 2;
@@ -44,6 +47,11 @@
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+
Context context = getApplicationContext();
int command = intent.getIntExtra(EXTRA_COMMAND, -1);
@@ -51,10 +59,11 @@
case COMMAND_START_FOREGROUND:
mNotificationId ++;
Log.d(TAG, "Starting foreground using notification " + mNotificationId);
- Notification notification = new Notification.Builder(context)
- .setContentTitle(getNotificationTitle(mNotificationId))
- .setSmallIcon(R.drawable.black)
- .build();
+ Notification notification =
+ new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getNotificationTitle(mNotificationId))
+ .setSmallIcon(R.drawable.black)
+ .build();
startForeground(mNotificationId, notification);
break;
case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
diff --git a/tests/app/app2/Android.mk b/tests/app/app2/Android.mk
new file mode 100644
index 0000000..ca1c509
--- /dev/null
+++ b/tests/app/app2/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+
+LOCAL_SRC_FILES := ../app/src/android/app/stubs/LocalService.java
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAppTestStubsDifferentUid
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/app2/AndroidManifest.xml b/tests/app/app2/AndroidManifest.xml
new file mode 100644
index 0000000..928771e
--- /dev/null
+++ b/tests/app/app2/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app2">
+ <application>
+ <service android:name="android.app.stubs.LocalService"
+ android:exported="true"/>
+ </application>
+</manifest>
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 910af71..74b5d6c 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -59,11 +59,13 @@
private static final String SERVICE_NAME = "android.app.stubs.MockService";
private static final int WAIT_TIME = 2000;
// A secondary test activity from another APK.
- private static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
- private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
- private static final String SIMPLE_ACTIVITY_IMMEDIATE_EXIT = ".SimpleActivityImmediateExit";
- private static final String SIMPLE_ACTIVITY_CHAIN_EXIT = ".SimpleActivityChainExit";
- private static final String SIMPLE_SERVICE = ".SimpleService";
+ static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
+ static final String SIMPLE_ACTIVITY = ".SimpleActivity";
+ static final String SIMPLE_ACTIVITY_IMMEDIATE_EXIT = ".SimpleActivityImmediateExit";
+ static final String SIMPLE_ACTIVITY_CHAIN_EXIT = ".SimpleActivityChainExit";
+ static final String SIMPLE_SERVICE = ".SimpleService";
+ static final String SIMPLE_RECEIVER = ".SimpleReceiver";
+ static final String SIMPLE_REMOTE_RECEIVER = ".SimpleRemoteReceiver";
// The action sent back by the SIMPLE_APP after a restart.
private static final String ACTIVITY_LAUNCHED_ACTION =
"com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
@@ -707,9 +709,6 @@
}
conn.unbind(WAIT_TIME);
- //cmd = "am kill " + STUB_PACKAGE_NAME;
- //result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
-
// Wait for uid's process to go away.
uidGoneListener.waitForValue(RunningAppProcessInfo.IMPORTANCE_GONE,
RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
@@ -719,6 +718,14 @@
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ // We don't want to wait for the uid to actually go idle, we can force it now.
+ cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ // Make sure app is not yet on whitelist
+ cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
// We will use this to monitor when the service is running.
conn.startMonitoring();
@@ -779,10 +786,13 @@
conn.waitForDisconnect(WAIT_TIME);
} finally {
+ mContext.stopService(serviceIntent);
conn.stopMonitoring();
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
am.removeOnUidImportanceListener(uidGoneListener);
am.removeOnUidImportanceListener(uidForegroundListener);
@@ -791,6 +801,36 @@
}
}
+ public void testDefaultProcessInstrumentation() throws Exception {
+ String cmd = "am instrument -w android.app.cts/.DefaultProcessInstrumentation";
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+ "\nINSTRUMENTATION_CODE: -1\n", result);
+ }
+
+ public void testAltProcessInstrumentation() throws Exception {
+ String cmd = "am instrument -w android.app.cts/.AltProcessInstrumentation";
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":other=true" +
+ "\nINSTRUMENTATION_CODE: -1\n", result);
+ }
+
+ public void testWildcardProcessInstrumentation() throws Exception {
+ String cmd = "am instrument -w android.app.cts/.WildcardProcessInstrumentation";
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+ "\nINSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":receiver=true" +
+ "\nINSTRUMENTATION_CODE: -1\n", result);
+ }
+
+ public void testMultiProcessInstrumentation() throws Exception {
+ String cmd = "am instrument -w android.app.cts/.MultiProcessInstrumentation";
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ assertEquals("INSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + "=true" +
+ "\nINSTRUMENTATION_RESULT: " + SIMPLE_PACKAGE_NAME + ":other=true" +
+ "\nINSTRUMENTATION_CODE: -1\n", result);
+ }
+
/**
* Verify that the TimeTrackingAPI works properly when starting and ending an activity.
*/
diff --git a/tests/app/src/android/app/cts/AltProcessInstrumentation.java b/tests/app/src/android/app/cts/AltProcessInstrumentation.java
new file mode 100644
index 0000000..59cd11c
--- /dev/null
+++ b/tests/app/src/android/app/cts/AltProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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 android.app.cts;
+
+public class AltProcessInstrumentation extends BaseProcessInstrumentation {
+ public AltProcessInstrumentation() {
+ super(ActivityManagerTest.SIMPLE_PACKAGE_NAME + ":other",
+ ActivityManagerTest.SIMPLE_PACKAGE_NAME
+ + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+ }
+}
diff --git a/tests/app/src/android/app/cts/BaseProcessInstrumentation.java b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
new file mode 100644
index 0000000..e816ac9
--- /dev/null
+++ b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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 android.app.cts;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class BaseProcessInstrumentation extends Instrumentation {
+ final String mMainProc;
+ final String mReceiverClass;
+
+ public BaseProcessInstrumentation(String mainProc, String receiverClass) {
+ mMainProc = mainProc;
+ mReceiverClass = receiverClass;
+ }
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ final String proc = getProcessName();
+ //Log.i("xxx", "Instrumentation starting in " + proc);
+ final Bundle result = new Bundle();
+ result.putBoolean(proc, true);
+ if (proc.equals(mMainProc)) {
+ // We are running in the main instr process... start a service that will launch
+ // a secondary proc.
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(ActivityManagerTest.SIMPLE_PACKAGE_NAME, mReceiverClass);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ //Log.i("xxx", "Instrumentation sending broadcast: " + intent);
+ getContext().sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ //Log.i("xxx", "Instrumentation finishing in " + proc);
+ finish(Activity.RESULT_OK, result);
+ }
+ }, null, 0, null, null);
+ } else {
+ // We are running in a secondary proc, just report it.
+ //Log.i("xxx", "Instrumentation adding result in " + proc);
+ addResults(result);
+ }
+ }
+}
diff --git a/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java b/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java
new file mode 100644
index 0000000..ca6503d
--- /dev/null
+++ b/tests/app/src/android/app/cts/DefaultProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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 android.app.cts;
+
+public class DefaultProcessInstrumentation extends BaseProcessInstrumentation {
+ public DefaultProcessInstrumentation() {
+ super(ActivityManagerTest.SIMPLE_PACKAGE_NAME,
+ ActivityManagerTest.SIMPLE_PACKAGE_NAME
+ + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+ }
+}
diff --git a/tests/app/src/android/app/cts/MultiProcessInstrumentation.java b/tests/app/src/android/app/cts/MultiProcessInstrumentation.java
new file mode 100644
index 0000000..4f3af4f
--- /dev/null
+++ b/tests/app/src/android/app/cts/MultiProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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 android.app.cts;
+
+public class MultiProcessInstrumentation extends BaseProcessInstrumentation {
+ public MultiProcessInstrumentation() {
+ super(ActivityManagerTest.SIMPLE_PACKAGE_NAME + ":other",
+ ActivityManagerTest.SIMPLE_PACKAGE_NAME
+ + ActivityManagerTest.SIMPLE_RECEIVER);
+ }
+}
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 1aeb1c6..a2df4bd 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -16,8 +16,11 @@
package android.app.cts;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.graphics.Color;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Parcel;
import android.test.AndroidTestCase;
@@ -47,7 +50,10 @@
assertEquals(null, channel.getVibrationPattern());
assertEquals(NotificationManager.IMPORTANCE_DEFAULT, channel.getImportance());
assertEquals(null, channel.getSound());
- assertFalse(channel.canShowBadge());
+ assertTrue(channel.canShowBadge());
+ assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes());
+ assertEquals(null, channel.getGroup());
+ assertTrue(channel.getLightColor() == 0);
}
public void testWriteToParcel() {
@@ -63,12 +69,20 @@
public void testLights() {
NotificationChannel channel =
new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
- channel.setLights(true);
+ channel.enableLights(true);
assertTrue(channel.shouldShowLights());
- channel.setLights(false);
+ channel.enableLights(false);
assertFalse(channel.shouldShowLights());
}
+ public void testLightColor() {
+ NotificationChannel channel =
+ new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setLightColor(Color.RED);
+ assertFalse(channel.shouldShowLights());
+ assertEquals(Color.RED, channel.getLightColor());
+ }
+
public void testVibration() {
NotificationChannel channel =
new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
@@ -85,17 +99,28 @@
assertNull(channel.getVibrationPattern());
channel.setVibrationPattern(pattern);
assertEquals(pattern, channel.getVibrationPattern());
- channel.enableVibration(true);
assertTrue(channel.shouldVibrate());
+
+ channel.setVibrationPattern(new long[]{});
+ assertEquals(false, channel.shouldVibrate());
+
+ channel.setVibrationPattern(null);
+ assertEquals(false, channel.shouldVibrate());
}
- public void testRingtone() {
+ public void testSound() {
Uri expected = new Uri.Builder().scheme("fruit").appendQueryParameter("favorite", "bananas")
.build();
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
NotificationChannel channel =
new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(expected);
+ channel.setSound(expected, attributes);
assertEquals(expected, channel.getSound());
+ assertEquals(attributes, channel.getAudioAttributes());
}
public void testShowBadge() {
@@ -104,4 +129,11 @@
channel.setShowBadge(true);
assertTrue(channel.canShowBadge());
}
+
+ public void testGroup() {
+ NotificationChannel channel =
+ new NotificationChannel("1", "one", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup("banana");
+ assertEquals("banana", channel.getGroup());
+ }
}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index b19e117..6b48128 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -24,6 +24,7 @@
import android.app.stubs.R;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.provider.Telephony.Threads;
import android.service.notification.StatusBarNotification;
@@ -77,8 +78,10 @@
new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
channel.enableVibration(true);
channel.setVibrationPattern(new long[] {5, 8, 2, 1});
- channel.setSound(new Uri.Builder().scheme("test").build());
- channel.setLights(true);
+ channel.setSound(new Uri.Builder().scheme("test").build(),
+ new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED).build());
+ channel.enableLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
mNotificationManager.createNotificationChannel(channel);
@@ -297,12 +300,13 @@
final int id = 128;
final long timeout = 1000;
- final Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.black)
- .setContentTitle("notify#" + id)
- .setContentText("This is #" + id + "notification ")
- .setTimeout(System.currentTimeMillis() + timeout)
- .build();
+ final Notification notification =
+ new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.black)
+ .setContentTitle("notify#" + id)
+ .setContentText("This is #" + id + "notification ")
+ .setTimeout(System.currentTimeMillis() + timeout)
+ .build();
mNotificationManager.notify(id, notification);
if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
@@ -382,5 +386,6 @@
assertEquals(expected.getSound(), actual.getSound());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
assertEquals(expected.getGroup(), actual.getGroup());
+ assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
}
}
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 1d608cf..c0877bc 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -18,6 +18,8 @@
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
@@ -42,6 +44,10 @@
private static final String URI_STRING = "uriString";
private static final String ACTION_TITLE = "actionTitle";
private static final int TOLERANCE = 200;
+ private static final long TIMEOUT = 4000;
+ private static final NotificationChannel CHANNEL = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ private static final String SHORTCUT_ID = "shortcutId";
@Override
protected void setUp() throws Exception {
@@ -62,6 +68,15 @@
assertEquals(notificationTime, mNotification.when);
assertEquals(0, mNotification.icon);
assertEquals(TICKER_TEXT, mNotification.tickerText);
+ assertEquals(1, mNotification.number);
+ }
+
+ public void testBuilderConstructor() {
+ mNotification = new Notification.Builder(mContext, CHANNEL.getId()).build();
+ assertEquals(CHANNEL.getId(), mNotification.getChannel());
+ assertEquals(Notification.BADGE_ICON_LARGE, mNotification.getBadgeIcon());
+ assertNull(mNotification.getShortcutId());
+ assertEquals((long) 0, mNotification.getTimeout());
}
public void testDescribeContents() {
@@ -71,7 +86,12 @@
}
public void testWriteToParcel() {
- mNotification = new Notification();
+
+ mNotification = new Notification.Builder(mContext, CHANNEL.getId())
+ .chooseBadgeIcon(Notification.BADGE_ICON_SMALL)
+ .setShortcutId(SHORTCUT_ID)
+ .setTimeout(TIMEOUT)
+ .build();
mNotification.icon = 0;
mNotification.number = 1;
final Intent intent = new Intent();
@@ -97,6 +117,7 @@
mNotification.ledOnMS = 0;
mNotification.ledOffMS = 0;
mNotification.iconLevel = 0;
+
Parcel parcel = Parcel.obtain();
mNotification.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -120,6 +141,10 @@
assertEquals(mNotification.ledOnMS, result.ledOnMS);
assertEquals(mNotification.ledOffMS, result.ledOffMS);
assertEquals(mNotification.iconLevel, result.iconLevel);
+ assertEquals(mNotification.getShortcutId(), result.getShortcutId());
+ assertEquals(mNotification.getBadgeIcon(), result.getBadgeIcon());
+ assertEquals(mNotification.getTimeout(), result.getTimeout());
+ assertEquals(mNotification.getChannel(), result.getChannel());
mNotification.contentIntent = null;
parcel = Parcel.obtain();
@@ -158,7 +183,7 @@
}
public void testColorizeNotification() {
- mNotification = new Notification.Builder(mContext)
+ mNotification = new Notification.Builder(mContext, "channel_id")
.setSmallIcon(1)
.setContentTitle(CONTENT_TITLE)
.setColorized(true)
@@ -170,17 +195,23 @@
public void testBuilder() {
final Intent intent = new Intent();
final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
- mNotification = new Notification.Builder(mContext, "channel_id")
+ mNotification = new Notification.Builder(mContext, CHANNEL.getId())
.setSmallIcon(1)
.setContentTitle(CONTENT_TITLE)
.setContentText(CONTENT_TEXT)
.setContentIntent(contentIntent)
+ .chooseBadgeIcon(Notification.BADGE_ICON_NONE)
+ .setShortcutId(SHORTCUT_ID)
+ .setTimeout(TIMEOUT)
.build();
assertEquals(CONTENT_TEXT, mNotification.extras.getString(Notification.EXTRA_TEXT));
assertEquals(CONTENT_TITLE, mNotification.extras.getString(Notification.EXTRA_TITLE));
assertEquals(1, mNotification.icon);
assertEquals(contentIntent, mNotification.contentIntent);
- assertEquals(mNotification.getChannel(), "channel_id");
+ assertEquals(CHANNEL.getId(), mNotification.getChannel());
+ assertEquals(Notification.BADGE_ICON_NONE, mNotification.getBadgeIcon());
+ assertEquals(SHORTCUT_ID, mNotification.getShortcutId());
+ assertEquals(TIMEOUT, mNotification.getTimeout());
}
public void testActionBuilder() {
@@ -194,7 +225,7 @@
}
public void testMessagingStyle_historicMessages() {
- mNotification = new Notification.Builder(mContext)
+ mNotification = new Notification.Builder(mContext, CHANNEL.getId())
.setSmallIcon(1)
.setContentTitle(CONTENT_TITLE)
.setStyle(new Notification.MessagingStyle("self name")
diff --git a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java b/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
index 7189dc1..e2a4aff 100644
--- a/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
+++ b/tests/app/src/android/app/cts/PipNotResizeableActivityTest.java
@@ -51,7 +51,6 @@
pipSupportDisabled = true;
}
assertTrue(pipSupportDisabled);
- assertFalse(mActivity.isInMultiWindowMode());
assertFalse(mActivity.isInPictureInPictureMode());
}
});
diff --git a/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java b/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
index e8d13dc..d33ff4d 100644
--- a/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
+++ b/tests/app/src/android/app/cts/PipNotSupportedActivityTest.java
@@ -51,7 +51,6 @@
pipSupportDisabled = true;
}
assertTrue(pipSupportDisabled);
- assertFalse(mActivity.isInMultiWindowMode());
assertFalse(mActivity.isInPictureInPictureMode());
}
});
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 40c323c..2922238 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,7 +16,9 @@
package android.app.cts;
+import android.app.ActivityManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.stubs.ActivityTestsBase;
import android.app.stubs.LocalDeniedService;
@@ -39,8 +41,11 @@
import com.android.compatibility.common.util.IBinderParcelable;
+import java.util.List;
+
public class ServiceTest extends ActivityTestsBase {
private static final String TAG = "ServiceTest";
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
private static final int STATE_START_1 = 0;
private static final int STATE_START_2 = 1;
private static final int STATE_START_3 = 2;
@@ -52,6 +57,8 @@
private static final
String EXIST_CONN_TO_RECEIVE_SERVICE = "existing connection to receive service";
private static final String EXIST_CONN_TO_LOSE_SERVICE = "existing connection to lose service";
+ private static final String EXTERNAL_SERVICE_COMPONENT =
+ "com.android.app2/android.app.stubs.LocalService";
private int mExpectedServiceState;
private Context mContext;
private Intent mLocalService;
@@ -60,6 +67,7 @@
private Intent mLocalGrantedService;
private Intent mLocalService_ApplicationHasPermission;
private Intent mLocalService_ApplicationDoesNotHavePermission;
+ private Intent mExternalService;
private IBinder mStateReceiver;
@@ -173,8 +181,8 @@
return notificationManager;
}
- private void sendNotififcation(int id, String title) {
- Notification notification = new Notification.Builder(getContext())
+ private void sendNotification(int id, String title) {
+ Notification notification = new Notification.Builder(getContext(), NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setSmallIcon(R.drawable.black)
.build();
@@ -383,6 +391,8 @@
super.setUp();
mContext = getContext();
mLocalService = new Intent(mContext, LocalService.class);
+ mExternalService = new Intent();
+ mExternalService.setComponent(ComponentName.unflattenFromString(EXTERNAL_SERVICE_COMPONENT));
mLocalForegroundService = new Intent(mContext, LocalForegroundService.class);
mLocalDeniedService = new Intent(mContext, LocalDeniedService.class);
mLocalGrantedService = new Intent(mContext, LocalGrantedService.class);
@@ -391,6 +401,19 @@
mLocalService_ApplicationDoesNotHavePermission = new Intent(
LocalService.SERVICE_LOCAL_DENIED, null /*uri*/, mContext, LocalService.class);
mStateReceiver = new MockBinder();
+ getNotificationManager().createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ getNotificationManager().deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+ mContext.stopService(mLocalService);
+ mContext.stopService(mLocalForegroundService);
+ mContext.stopService(mLocalGrantedService);
+ mContext.stopService(mLocalService_ApplicationHasPermission);
+ mContext.stopService(mExternalService);
}
private class MockBinder extends Binder {
@@ -461,14 +484,6 @@
}
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mContext.stopService(mLocalService);
- mContext.stopService(mLocalForegroundService);
- mContext.stopService(mLocalGrantedService);
- mContext.stopService(mLocalService_ApplicationHasPermission);
- }
public void testLocalStartClass() throws Exception {
startExpectResult(mLocalService);
@@ -507,7 +522,7 @@
// Sends another notification reusing the same notification id.
String newTitle = "YODA I AM";
- sendNotififcation(1, newTitle);
+ sendNotification(1, newTitle);
assertNotification(1, newTitle);
// Start service as foreground again - it should kill notification #1 and show #2
@@ -581,6 +596,53 @@
assertNoNotification(2);
}
+ public void testRunningServices() throws Exception {
+ final int maxReturnedServices = 10;
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(mStateReceiver));
+
+ boolean success = false;
+
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+ // No services should be reported back at the beginning
+ assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+ try {
+ mExpectedServiceState = STATE_START_1;
+ // Start external service.
+ mContext.startService(new Intent(mExternalService).putExtras(bundle));
+ waitForResultOrThrow(DELAY, "external service to start first time");
+
+ // Ensure we can't see service.
+ assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+
+ // Start local service.
+ mContext.startService(new Intent(mLocalService).putExtras(bundle));
+ waitForResultOrThrow(DELAY, "local service to start first time");
+ success = true;
+
+ // Ensure we can see service and it is ours.
+ List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(maxReturnedServices);
+ assertEquals(1, services.size());
+ assertEquals(android.os.Process.myUid(), services.get(0).uid);
+ } finally {
+ if (!success) {
+ mContext.stopService(mLocalService);
+ mContext.stopService(mExternalService);
+ }
+ }
+ mExpectedServiceState = STATE_DESTROY;
+
+ mContext.stopService(mExternalService);
+ waitForResultOrThrow(DELAY, "external service to be destroyed");
+
+ mContext.stopService(mLocalService);
+ waitForResultOrThrow(DELAY, "local service to be destroyed");
+
+ // Once our service has stopped, make sure we can't see any services.
+ assertEquals(0, am.getRunningServices(maxReturnedServices).size());
+ }
+
@MediumTest
public void testForegroundService_detachNotificationOnStop() throws Exception {
String newTitle = null;
@@ -602,7 +664,7 @@
// Sends another notification reusing the same notification id.
newTitle = "YODA I AM";
- sendNotififcation(1, newTitle);
+ sendNotification(1, newTitle);
assertNotification(1, newTitle);
// Start service as foreground again - it should show notification #2..
diff --git a/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java b/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java
new file mode 100644
index 0000000..e854e76
--- /dev/null
+++ b/tests/app/src/android/app/cts/WildcardProcessInstrumentation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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 android.app.cts;
+
+public class WildcardProcessInstrumentation extends BaseProcessInstrumentation {
+ public WildcardProcessInstrumentation() {
+ super(ActivityManagerTest.SIMPLE_PACKAGE_NAME,
+ ActivityManagerTest.SIMPLE_PACKAGE_NAME
+ + ActivityManagerTest.SIMPLE_REMOTE_RECEIVER);
+ }
+}
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index 6a8957da..a3601fc 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -51,6 +51,7 @@
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:text="TopSecret"
android:inputType="textPassword" />
</LinearLayout>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 8a90892..7c412bb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.UI_TIMEOUT_SEC;
+import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.runShellCommand;
import static android.provider.Settings.Secure.AUTO_FILL_SERVICE;
@@ -44,7 +44,7 @@
@BeforeClass
public static void setUiBot() throws Exception {
- sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_SEC);
+ sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation(), UI_TIMEOUT_MS);
}
@AfterClass
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 14ab6a3..4f41e33 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -83,24 +83,28 @@
*/
static class CannedDataset {
- final Map<String, AutoFillValue> fields;
+ final Map<String, Field> fields;
+ final String id;
final String name;
private CannedDataset(Builder builder) {
fields = builder.mFields;
+ id = builder.mId;
name = builder.mName;
}
@Override
public String toString() {
- return "CannedDataset: [name=" + name + ", fields=" + fields + "]";
+ return "CannedDataset: [id=" + id + ", name=" + name + ", fields=" + fields + "]";
}
static class Builder {
- private final Map<String, AutoFillValue> mFields = new HashMap<>();
+ private final Map<String, Field> mFields = new HashMap<>();
+ private final String mId;
private final String mName;
- public Builder(String name) {
+ public Builder(String id, String name) {
+ mId = id;
mName = name;
}
@@ -108,7 +112,16 @@
* Sets the canned value of a field based on its {@code resourceId}.
*/
public Builder setField(String resourceId, AutoFillValue value) {
- mFields.put(resourceId, value);
+ mFields.put(resourceId, new Field(value));
+ return this;
+ }
+
+ /**
+ * Sets a canned value of a field based on its {@code resourceId}, and asserts its
+ * sanitized.
+ */
+ public Builder setSanitizedField(String resourceId, AutoFillValue value) {
+ mFields.put(resourceId, new Field(value, true));
return this;
}
@@ -117,4 +130,23 @@
}
}
}
+
+ static class Field {
+ final boolean sanitized;
+ final AutoFillValue value;
+
+ Field(AutoFillValue value, boolean sanitized) {
+ this.value = value;
+ this.sanitized = sanitized;
+ }
+
+ Field(AutoFillValue value) {
+ this(value, false);
+ }
+
+ @Override
+ public String toString() {
+ return value + (sanitized ? " (sanitized)" : "");
+ }
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 341a921..6559150 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -35,9 +35,9 @@
static final long FILL_TIMEOUT_MS = 2000;
/**
- * Timeout (in seconds) for UI operations. Typically used by {@link UiBot}.
+ * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}.
*/
- static final int UI_TIMEOUT_SEC = 2;
+ static final int UI_TIMEOUT_MS = 2000;
/**
* Runs a Shell command, returning a trimmed response.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index c679df2..a14d7ed 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -21,11 +21,13 @@
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.CannedFillResponse.Field;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.service.autofill.AutoFillService;
import android.service.autofill.FillCallback;
import android.service.autofill.SaveCallback;
+import android.text.TextUtils;
import android.util.Log;
import android.view.autofill.AutoFillId;
import android.view.autofill.AutoFillValue;
@@ -69,11 +71,13 @@
final CannedFillResponse cannedResponse = sCannedFillResponse.getAndSet(null);
Log.v(TAG, "onFillRequest(#" + requestNumber + "): cannedResponse = " + cannedResponse);
+ assertWithMessage("CancelationSignal is null").that(cancellationSignal).isNotNull();
+
if (cannedResponse == null) {
callback.onSuccess(null);
return;
}
- final FillResponse.Builder responseBuilder = new FillResponse.Builder();
+ final FillResponse.Builder responseBuilder = new FillResponse.Builder("4815162342");
final List<CannedDataset> datasets = cannedResponse.datasets;
if (datasets.isEmpty()) {
@@ -85,13 +89,13 @@
final CannedDataset dataset = datasets.get(0);
- final Map<String, AutoFillValue> fields = dataset.fields;
+ final Map<String, Field> fields = dataset.fields;
if (fields.isEmpty()) {
callback.onSuccess(responseBuilder.build());
return;
}
- final Dataset.Builder datasetBuilder = new Dataset.Builder(dataset.name);
+ final Dataset.Builder datasetBuilder = new Dataset.Builder(dataset.id, dataset.name);
Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
final int nodes = structure.getWindowNodeCount();
@@ -141,15 +145,29 @@
assertWithMessage("Invalid number of fill requests").that(actual).isEqualTo(expected);
}
- private void fill(Dataset.Builder builder, Map<String, AutoFillValue> fields,
+ private void fill(Dataset.Builder builder, Map<String, Field> fields,
ViewNode view) {
final String resourceId = view.getIdEntry();
+ final Field field = fields.get(resourceId);
- final AutoFillValue value = fields.get(resourceId);
- if (value != null) {
- final AutoFillId id = view.getAutoFillId();
- Log.d(TAG, "setting '" + resourceId + "' (" + id + ") to " + value);
- builder.setValue(id, value);
+ if (field != null) {
+ // Make sure it's sanitized
+ if (field.sanitized) {
+ final CharSequence text = view.getText();
+ if (!TextUtils.isEmpty(text)) {
+ throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
+ }
+ final AutoFillValue initialValue = view.getAutoFillValue();
+ assertWithMessage("auto-fill value on sanitized field %s: %s", resourceId,
+ initialValue).that(initialValue).isNull();
+ }
+
+ final AutoFillValue value = field.value;
+ if (value != null) {
+ final AutoFillId id = view.getAutoFillId();
+ Log.d(TAG, "setting '" + resourceId + "' (" + id + ") to " + value);
+ builder.setValue(id, value);
+ }
}
final int childrenSize = view.getChildCount();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 9d94c99..9bc1dd2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -114,14 +114,8 @@
/**
* Sets the expectation for an auto-fill request, so it can be asserted through
* {@link #assertAutoFilled()} later.
- *
- * <p>It fills the {@code builder} dataset with the proper fields for {@code} username and
- * {@code password}, so caller can use it to set a {@link CannedFillResponse}.
*/
- void expectAutoFill(CannedDataset.Builder builder, String username, String password) {
- builder
- .setField(ID_USERNAME, AutoFillValue.forText(username))
- .setField(ID_PASSWORD, AutoFillValue.forText(password));
+ void expectAutoFill(String username, String password) {
mAutoFillExpectation = new AutoFillExpectation(username, password);
mUsernameEditText
.addTextChangedListener(new MyTextWatcher(mAutoFillExpectation.usernameLatch));
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index ba8ff95..fe457ca 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -21,47 +21,42 @@
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
+import android.view.autofill.AutoFillValue;
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
@SmallTest
public class LoginActivityTest extends AutoFillServiceTestCase {
- @Rule
+ // TODO(b/33197203): fix logic so it can use @Rule...
+ // Cannot use @Rule because must set service before launching activity
public final ActivityTestRule<LoginActivity> mActivityRule =
new ActivityTestRule<LoginActivity>(LoginActivity.class);
private LoginActivity mLoginActivity;
- @Before
- public void setActivity() {
- mLoginActivity = mActivityRule.getActivity();
- }
-
@Test
public void testAutoFillOneDataset() throws Exception {
enableService();
- final CannedDataset.Builder dataset = new CannedDataset.Builder("The Dude");
- mLoginActivity.expectAutoFill(dataset, "dude", "sweet");
+ final CannedDataset.Builder dataset = new CannedDataset.Builder("4815162342", "The Dude")
+ .setField(ID_USERNAME, AutoFillValue.forText("dude"))
+ .setSanitizedField(ID_PASSWORD, AutoFillValue.forText("sweet"));
InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
.addDataset(dataset.build())
.build());
- sUiBot.triggerImeByRelativeId(ID_USERNAME);
- // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
- sUiBot.collapseStatusBar();
+ mLoginActivity = mActivityRule.launchActivity(null);
+ mLoginActivity.expectAutoFill("dude", "sweet");
+ // TODO(b/33197203): Add this logic back in the test.
// Make sure tapping on other fields from the dataset does not trigger it again
- sUiBot.tapByRelativeId(ID_PASSWORD);
- sUiBot.tapByRelativeId(ID_USERNAME);
-
- // TODO(b/33197203, b/33802548): temporary hack because UI is based on notifications
- sUiBot.expandStatusBar();
+ if (false) {
+ sUiBot.tapByRelativeId(ID_PASSWORD);
+ sUiBot.tapByRelativeId(ID_USERNAME);
+ }
sUiBot.selectDataset("The Dude");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 89e5e02..497644f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -20,8 +20,10 @@
import android.app.Instrumentation;
import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
@@ -42,18 +44,17 @@
mDevice = UiDevice.getInstance(instrumentation);
mTimeout = timeout;
mPackageName = instrumentation.getContext().getPackageName();
-
- collapseStatusBar();
}
/**
* Selects an auto-fill dataset whose name should be visible in the UI.
*/
void selectDataset(String name) {
- // TODO(b/33197203): use id string when using real auto-fill bar
Log.v(TAG, "selectDataset(): " + name);
- clickOnNotification(name.toUpperCase());
+ // TODO(b/33197203): Use more qualified ids for UI.
+ final UiObject2 dataset = waitForObject(By.res("android", "text1").text(name));
+ dataset.click();
}
/**
@@ -82,81 +83,19 @@
assertWithMessage("Failed to tap object with id '%s'", fullId).that(clicked).isTrue();
}
- /////////////////////////////////////////////////////////////////////////////////
- // TODO(b/33197203): temporary code using a notification to request auto-fill. //
- // Will be removed once UX decide the right way to present it to the user. //
- /////////////////////////////////////////////////////////////////////////////////
- private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
-
/**
- * Clicks on a UI element.
+ * Waits for and returns an object.
*
- * @param uiObject UI element to be clicked.
- * @param description Elements's description used on logging statements.
+ * @param selector {@link BySelector} that identifies the object.
*/
- private void click(UiObject uiObject, String description) {
- try {
- boolean clicked = uiObject.click();
- // TODO: assertion below fails sometimes, even though the click succeeded,
- // (specially when clicking the "Just Once" button), so it's currently just logged.
- // assertTrue("could not click on object '" + description + "'", clicked);
+ private UiObject2 waitForObject(BySelector selector) {
+ final boolean gotIt = mDevice.wait(Until.hasObject(selector), mTimeout);
+ assertWithMessage("object for '%s' not found in %s ms", selector, mTimeout).that(gotIt)
+ .isTrue();
- Log.v(TAG, "onClick for " + description + ": " + clicked);
- } catch (UiObjectNotFoundException e) {
- throw new IllegalStateException("exception when clicking on object '" + description
- + "'", e);
- }
- }
-
- /**
- * Opens the system notification and clicks a given notification.
- *
- * @param text Notificaton's text as displayed by the UI.
- */
- private void clickOnNotification(String text) {
- final UiObject notification = getNotification(text);
- click(notification, "notification '" + text+ "'");
- }
-
- private UiObject getNotification(String text) {
- final boolean opened = mDevice.openNotification();
- Log.v(TAG, "openNotification(): " + opened);
- final boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
- assertWithMessage("could not get system ui (%s)", SYSTEMUI_PACKAGE).that(gotIt).isTrue();
-
- return getObject(text);
- }
- /**
- * Gets an object that might not yet be available in current UI.
- *
- * @param text Object's text as displayed by the UI.
- */
- private UiObject getObject(String text) {
- final boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
- assertWithMessage("object with text '%s') not visible yet", text).that(gotIt).isTrue();
- return getVisibleObject(text);
- }
-
- /**
- * Gets an object which is guaranteed to be present in the current UI.
- *
- * @param text Object's text as displayed by the UI.
- */
- private UiObject getVisibleObject(String text) {
- final UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
- assertWithMessage("could not find object with '%s'", text).that(uiObject.exists()).isTrue();
+ final UiObject2 uiObject = mDevice.findObject(selector);
+ assertWithMessage("object for '%s' null in %s ms", selector, mTimeout).that(uiObject)
+ .isNotNull();
return uiObject;
}
-
- void collapseStatusBar() throws Exception {
- Helper.runShellCommand("service call statusbar 2");
- }
-
- void expandStatusBar() throws Exception {
- Helper.runShellCommand("service call statusbar 1");
- }
-
- /////////////////////////////////////////
- // End of temporary notification code. //
- /////////////////////////////////////////
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 2357f54..84f5ddc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -350,27 +350,25 @@
String.format("Surfaces returned from getSurfaces() don't match those passed in"),
previewSurfaces.equals(inputSurfaces));
- // Verify that outputConfiguration throws exception if 2 surfaces are different size
+ // Verify that createCaptureSession fails if 2 surfaces are different size
SurfaceTexture outputTexture2 = new SurfaceTexture(/* random texture ID*/ 5);
outputTexture2.setDefaultBufferSize(previewSize.getWidth()/2,
previewSize.getHeight()/2);
Surface outputSurface2 = new Surface(outputTexture2);
- try {
- OutputConfiguration configuration = new OutputConfiguration(
- OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
- configuration.enableSurfaceSharing();
- configuration.addSurface(outputSurface2);
- fail("No error for invalid output config created from different sizes of surfaces");
- } catch (IllegalArgumentException e) {
- // expected
- }
+ OutputConfiguration configuration = new OutputConfiguration(
+ OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ configuration.enableSurfaceSharing();
+ configuration.addSurface(outputSurface2);
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(configuration);
+ verifyCreateSessionWithConfigsFailure(cameraId, outputConfigurations);
// Verify that outputConfiguration throws exception if 2 surfaces are different format
ImageReader imageReader = makeImageReader(previewSize, ImageFormat.YUV_420_888,
MAX_READER_IMAGES, new ImageDropperListener(), mHandler);
try {
- OutputConfiguration configuration = new OutputConfiguration(
- OutputConfiguration.SURFACE_GROUP_ID_NONE, surfaces[0]);
+ configuration = new OutputConfiguration(OutputConfiguration.SURFACE_GROUP_ID_NONE,
+ surfaces[0]);
configuration.enableSurfaceSharing();
configuration.addSurface(imageReader.getSurface());
fail("No error for invalid output config created from different format surfaces");
@@ -392,8 +390,7 @@
// Verify that outputConfiguration throws exception if deferred surface and non-deferred
// surface properties don't match
try {
- OutputConfiguration configuration = new OutputConfiguration(
- previewSize, SurfaceTexture.class);
+ configuration = new OutputConfiguration(previewSize, SurfaceTexture.class);
configuration.addSurface(imageReader.getSurface());
fail("No error for invalid output config created deferred class with different type");
} catch (IllegalArgumentException e) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index 2538645..2ae9578 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -503,6 +503,17 @@
}
}
+ // Confirm that zero surface size isn't supported for OutputConfiguration
+ Size[] sizeZeros = { new Size(0, 0), new Size(1, 0), new Size(0, 1) };
+ for (Size size : sizeZeros) {
+ try {
+ OutputConfiguration bad = new OutputConfiguration(size, SurfaceHolder.class);
+ fail("OutputConfiguration allowed use of zero surfaceSize");
+ } catch (IllegalArgumentException e) {
+ //expected
+ }
+ }
+
// Create session
BlockingSessionCallback sessionListener =
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index a8180d1..a1d63d8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -52,6 +52,7 @@
import junit.framework.Assert;
+import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
@@ -279,6 +280,12 @@
return camera.getOrderedPreviewSizes();
}
+ protected void verifyCreateSessionWithConfigsFailure(String cameraId,
+ List<OutputConfiguration> configs) throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ camera.verifyCreateSessionWithConfigsFailure(configs);
+ }
+
/**
* Wait until the SurfaceTexture available from the TextureView, then return it.
* Return null if the wait times out.
@@ -436,6 +443,20 @@
mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, mSessionListener, mHandler);
}
+ public void verifyCreateSessionWithConfigsFailure(List<OutputConfiguration> configs)
+ throws Exception {
+ BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+ CameraCaptureSession session = configureCameraSessionWithConfig(
+ mCamera, configs, sessionListener, mHandler);
+
+ Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
+ BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
+ int state = sessionListener.getStateWaiter().waitForAnyOfStates(
+ Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
+ assertTrue("Expecting a createSessionWithConfig failure.",
+ state == BlockingSessionCallback.SESSION_CONFIGURE_FAILED);
+ }
+
public void startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
CaptureCallback listener)
throws Exception {
diff --git a/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java b/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
index 4034991..6f049c0 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentLifecycleTest.java
@@ -441,6 +441,90 @@
assertFalse(fragment1.mCalledOnResume);
}
+ @Test
+ public void testIsStateSaved() throws Throwable {
+ FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+ FragmentTestUtil.resume(mActivityRule, fc, null);
+ FragmentManager fm = fc.getFragmentManager();
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Fragment f = new StrictFragment();
+ fm.beginTransaction()
+ .add(f, "1")
+ .commitNow();
+
+ assertFalse("fragment reported state saved while resumed",
+ f.isStateSaved());
+
+ fc.dispatchPause();
+ fc.saveAllState();
+
+ assertTrue("fragment reported state not saved after saveAllState",
+ f.isStateSaved());
+
+ fc.dispatchStop();
+
+ assertTrue("fragment reported state not saved after stop",
+ f.isStateSaved());
+
+ fc.dispatchDestroy();
+
+ assertFalse("fragment reported state saved after destroy",
+ f.isStateSaved());
+ }
+ });
+ }
+
+ @Test
+ public void testSetArgumentsLifecycle() throws Throwable {
+ FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+ FragmentTestUtil.resume(mActivityRule, fc, null);
+ FragmentManager fm = fc.getFragmentManager();
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Fragment f = new StrictFragment();
+ f.setArguments(new Bundle());
+
+ fm.beginTransaction()
+ .add(f, "1")
+ .commitNow();
+
+ f.setArguments(new Bundle());
+
+ fc.dispatchPause();
+ fc.saveAllState();
+
+ boolean threw = false;
+ try {
+ f.setArguments(new Bundle());
+ } catch (IllegalStateException ise) {
+ threw = true;
+ }
+ assertTrue("fragment allowed setArguments after state save", threw);
+
+ fc.dispatchStop();
+
+ threw = false;
+ try {
+ f.setArguments(new Bundle());
+ } catch (IllegalStateException ise) {
+ threw = true;
+ }
+ assertTrue("fragment allowed setArguments after stop", threw);
+
+ fc.dispatchDestroy();
+
+ // Fully destroyed, so fragments have been removed.
+ f.setArguments(new Bundle());
+ }
+ });
+
+ }
+
/*
* Test that target fragments are in a useful state when we restore them, even if they're
* on the back stack.
@@ -596,6 +680,15 @@
}
}
+ @Test
+ public void targetFragmentSetClear() throws Throwable {
+ final Fragment one = new Fragment();
+ final Fragment two = new Fragment();
+
+ one.setTargetFragment(two, 0);
+ one.setTargetFragment(null, 0);
+ }
+
private void executePendingTransactions(final FragmentManager fm) throws Throwable {
mActivityRule.runOnUiThread(new Runnable() {
@Override
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
index 3a69b44..5566318 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentTestUtil.java
@@ -17,8 +17,10 @@
import static org.junit.Assert.assertEquals;
+import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentController;
+import android.app.FragmentManager;
import android.app.FragmentManagerNonConfig;
import android.app.Instrumentation;
import android.os.Parcelable;
@@ -40,26 +42,43 @@
instrumentation.runOnMainSync(() -> {});
}
+ private static void runOnUiThreadRethrow(ActivityTestRule<FragmentTestActivity> rule,
+ Runnable r) {
+ try {
+ rule.runOnUiThread(r);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
public static boolean executePendingTransactions(
final ActivityTestRule<FragmentTestActivity> rule) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ return executePendingTransactions(rule, rule.getActivity().getFragmentManager());
+ }
+
+ public static boolean executePendingTransactions(
+ final ActivityTestRule<FragmentTestActivity> rule, final FragmentManager fm) {
final boolean[] ret = new boolean[1];
- instrumentation.runOnMainSync(new Runnable() {
+ runOnUiThreadRethrow(rule, new Runnable() {
@Override
public void run() {
- ret[0] = rule.getActivity().getFragmentManager().executePendingTransactions();
+ ret[0] = fm.executePendingTransactions();
}
});
return ret[0];
}
public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ return popBackStackImmediate(rule, rule.getActivity().getFragmentManager());
+ }
+
+ public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+ final FragmentManager fm) {
final boolean[] ret = new boolean[1];
- instrumentation.runOnMainSync(new Runnable() {
+ runOnUiThreadRethrow(rule, new Runnable() {
@Override
public void run() {
- ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate();
+ ret[0] = fm.popBackStackImmediate();
}
});
return ret[0];
@@ -67,12 +86,16 @@
public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
final int id, final int flags) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ return popBackStackImmediate(rule, rule.getActivity().getFragmentManager(), id, flags);
+ }
+
+ public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+ final FragmentManager fm, final int id, final int flags) {
final boolean[] ret = new boolean[1];
- instrumentation.runOnMainSync(new Runnable() {
+ runOnUiThreadRethrow(rule, new Runnable() {
@Override
public void run() {
- ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate(id, flags);
+ ret[0] = fm.popBackStackImmediate(id, flags);
}
});
return ret[0];
@@ -80,12 +103,16 @@
public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
final String name, final int flags) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ return popBackStackImmediate(rule, rule.getActivity().getFragmentManager(), name, flags);
+ }
+
+ public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule,
+ final FragmentManager fm, final String name, final int flags) {
final boolean[] ret = new boolean[1];
- instrumentation.runOnMainSync(new Runnable() {
+ runOnUiThreadRethrow(rule, new Runnable() {
@Override
public void run() {
- ret[0] = rule.getActivity().getFragmentManager().popBackStackImmediate(name, flags);
+ ret[0] = fm.popBackStackImmediate(name, flags);
}
});
return ret[0];
@@ -93,11 +120,11 @@
public static void setContentView(final ActivityTestRule<FragmentTestActivity> rule,
final int layoutId) {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.runOnMainSync(new Runnable() {
+ final Activity activity = rule.getActivity();
+ runOnUiThreadRethrow(rule, new Runnable() {
@Override
public void run() {
- rule.getActivity().setContentView(layoutId);
+ activity.setContentView(layoutId);
}
});
}
diff --git a/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java b/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java
new file mode 100644
index 0000000..a25ceb9
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/FragmentTransactionTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 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 android.fragment.cts;
+
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests usage of the {@link FragmentTransaction} class.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FragmentTransactionTest {
+
+ @Rule
+ public ActivityTestRule<FragmentTestActivity> mActivityRule =
+ new ActivityTestRule<>(FragmentTestActivity.class);
+
+ private FragmentTestActivity mActivity;
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testAddTransactionWithValidFragment() throws Throwable {
+ final Fragment fragment = new CorrectFragment();
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(android.R.id.content, fragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ assertTrue(fragment.isAdded());
+ }
+
+ @Test
+ public void testAddTransactionWithPrivateFragment() throws Throwable {
+ final Fragment fragment = new PrivateFragment();
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean exceptionThrown = false;
+ try {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(android.R.id.content, fragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ } catch (IllegalStateException e) {
+ exceptionThrown = true;
+ } finally {
+ assertTrue("Exception should be thrown", exceptionThrown);
+ assertFalse("Fragment shouldn't be added", fragment.isAdded());
+ }
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testAddTransactionWithPackagePrivateFragment() throws Throwable {
+ final Fragment fragment = new PackagePrivateFragment();
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean exceptionThrown = false;
+ try {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(android.R.id.content, fragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ } catch (IllegalStateException e) {
+ exceptionThrown = true;
+ } finally {
+ assertTrue("Exception should be thrown", exceptionThrown);
+ assertFalse("Fragment shouldn't be added", fragment.isAdded());
+ }
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testAddTransactionWithAnonymousFragment() throws Throwable {
+ final Fragment fragment = new Fragment() {};
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean exceptionThrown = false;
+ try {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(android.R.id.content, fragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ } catch (IllegalStateException e) {
+ exceptionThrown = true;
+ } finally {
+ assertTrue("Exception should be thrown", exceptionThrown);
+ assertFalse("Fragment shouldn't be added", fragment.isAdded());
+ }
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testAddTransactionWithNonStaticFragment() throws Throwable {
+ final Fragment fragment = new NonStaticFragment();
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean exceptionThrown = false;
+ try {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(android.R.id.content, fragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ } catch (IllegalStateException e) {
+ exceptionThrown = true;
+ } finally {
+ assertTrue("Exception should be thrown", exceptionThrown);
+ assertFalse("Fragment shouldn't be added", fragment.isAdded());
+ }
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testPostOnCommit() throws Throwable {
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final boolean[] ran = new boolean[1];
+ FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+ fm.beginTransaction().postOnCommit(new Runnable() {
+ @Override
+ public void run() {
+ ran[0] = true;
+ }
+ }).commit();
+ fm.executePendingTransactions();
+
+ assertTrue("postOnCommit runnable never ran", ran[0]);
+
+ ran[0] = false;
+
+ boolean threw = false;
+ try {
+ fm.beginTransaction().postOnCommit(new Runnable() {
+ @Override
+ public void run() {
+ ran[0] = true;
+ }
+ }).addToBackStack(null).commit();
+ } catch (IllegalStateException ise) {
+ threw = true;
+ }
+
+ fm.executePendingTransactions();
+
+ assertTrue("postOnCommit was allowed to be called for back stack transaction",
+ threw);
+ assertFalse("postOnCommit runnable for back stack transaction was run", ran[0]);
+ }
+ });
+ }
+
+ public static class CorrectFragment extends Fragment {}
+
+ private static class PrivateFragment extends Fragment {}
+
+ static class PackagePrivateFragment extends Fragment {}
+
+ private class NonStaticFragment extends Fragment {}
+}
diff --git a/tests/fragment/src/android/fragment/cts/FragmentViewTests.java b/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
index c12fb30..6f96811 100644
--- a/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
+++ b/tests/fragment/src/android/fragment/cts/FragmentViewTests.java
@@ -999,6 +999,51 @@
FragmentTestUtil.assertChildren(innerContainer, fragment2);
}
+ // Popping the backstack with non-optimized fragments should execute the operations together.
+ // When a non-backstack fragment will be raised, it should not be destroyed.
+ @Test
+ public void popToNonBackStackFragment() throws Throwable {
+ FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+ final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+
+ final SimpleViewFragment fragment1 = new SimpleViewFragment();
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+ final SimpleViewFragment fragment2 = new SimpleViewFragment();
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack("two")
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+ final SimpleViewFragment fragment3 = new SimpleViewFragment();
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack("three")
+ .commit();
+
+ FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+ FragmentTestUtil.popBackStackImmediate(mActivityRule, "two",
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+ ViewGroup container = (ViewGroup)
+ mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+
+ FragmentTestUtil.assertChildren(container, fragment1);
+ assertEquals(2, fragment1.onCreateViewCount);
+ assertEquals(1, fragment2.onCreateViewCount);
+ assertEquals(1, fragment3.onCreateViewCount);
+ }
+
private View findViewById(int viewId) {
return mActivityRule.getActivity().findViewById(viewId);
}
@@ -1043,4 +1088,15 @@
return view;
}
}
+
+ public static class SimpleViewFragment extends Fragment {
+ public int onCreateViewCount;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ onCreateViewCount++;
+ return inflater.inflate(R.layout.text_a, container, false);
+ }
+ }
}
diff --git a/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java b/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java
new file mode 100644
index 0000000..c5c11cf
--- /dev/null
+++ b/tests/fragment/src/android/fragment/cts/PrimaryNavFragmentTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2017 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 android.fragment.cts;
+
+import android.app.FragmentManager;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.fragment.cts.FragmentTestUtil.executePendingTransactions;
+import static android.fragment.cts.FragmentTestUtil.popBackStackImmediate;
+import static junit.framework.TestCase.*;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PrimaryNavFragmentTest {
+ @Rule
+ public ActivityTestRule<FragmentTestActivity> mActivityRule =
+ new ActivityTestRule<>(FragmentTestActivity.class);
+
+ @Test
+ public void delegateBackToPrimaryNav() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+ final StrictFragment strictFragment = new StrictFragment();
+
+ fm.beginTransaction().add(strictFragment, null).setPrimaryNavigationFragment(strictFragment)
+ .commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("new fragment is not primary nav fragment", strictFragment,
+ fm.getPrimaryNavigationFragment());
+
+ final StrictFragment child = new StrictFragment();
+ FragmentManager cfm = strictFragment.getChildFragmentManager();
+ cfm.beginTransaction().add(child, null).addToBackStack(null).commit();
+ executePendingTransactions(mActivityRule, cfm);
+
+ assertEquals("child transaction not on back stack", 1, cfm.getBackStackEntryCount());
+
+ // Should execute the pop for the child fragmentmanager
+ assertTrue("popBackStackImmediate returned no action performed",
+ popBackStackImmediate(mActivityRule, fm));
+
+ assertEquals("child transaction still on back stack", 0, cfm.getBackStackEntryCount());
+ }
+
+ @Test
+ public void popPrimaryNav() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+ final StrictFragment strictFragment1 = new StrictFragment();
+
+ fm.beginTransaction().add(strictFragment1, null)
+ .setPrimaryNavigationFragment(strictFragment1)
+ .commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("new fragment is not primary nav fragment", strictFragment1,
+ fm.getPrimaryNavigationFragment());
+
+ fm.beginTransaction().remove(strictFragment1).addToBackStack(null).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertNull("primary nav fragment is not null after remove",
+ fm.getPrimaryNavigationFragment());
+
+ popBackStackImmediate(mActivityRule, fm);
+
+ assertSame("primary nav fragment was not restored on pop", strictFragment1,
+ fm.getPrimaryNavigationFragment());
+
+ final StrictFragment strictFragment2 = new StrictFragment();
+ fm.beginTransaction().remove(strictFragment1).add(strictFragment2, null)
+ .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("primary nav fragment not updated to new fragment", strictFragment2,
+ fm.getPrimaryNavigationFragment());
+
+ popBackStackImmediate(mActivityRule, fm);
+
+ assertSame("primary nav fragment not restored on pop", strictFragment1,
+ fm.getPrimaryNavigationFragment());
+
+ fm.beginTransaction().setPrimaryNavigationFragment(strictFragment1)
+ .addToBackStack(null).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("primary nav fragment not retained when set again in new transaction",
+ strictFragment1, fm.getPrimaryNavigationFragment());
+ popBackStackImmediate(mActivityRule, fm);
+
+ assertSame("same primary nav fragment not retained when set primary nav transaction popped",
+ strictFragment1, fm.getPrimaryNavigationFragment());
+ }
+
+ @Test
+ public void replacePrimaryNav() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getFragmentManager();
+ final StrictFragment strictFragment1 = new StrictFragment();
+
+ fm.beginTransaction().add(android.R.id.content, strictFragment1)
+ .setPrimaryNavigationFragment(strictFragment1).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("new fragment is not primary nav fragment", strictFragment1,
+ fm.getPrimaryNavigationFragment());
+
+ final StrictFragment strictFragment2 = new StrictFragment();
+ fm.beginTransaction().replace(android.R.id.content, strictFragment2)
+ .addToBackStack(null).commit();
+
+ executePendingTransactions(mActivityRule, fm);
+
+ assertNull("primary nav fragment not null after replace",
+ fm.getPrimaryNavigationFragment());
+
+ popBackStackImmediate(mActivityRule, fm);
+
+ assertSame("primary nav fragment not restored after popping replace", strictFragment1,
+ fm.getPrimaryNavigationFragment());
+
+ fm.beginTransaction().setPrimaryNavigationFragment(null).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertNull("primary nav fragment not null after explicit set to null",
+ fm.getPrimaryNavigationFragment());
+
+ fm.beginTransaction().replace(android.R.id.content, strictFragment2)
+ .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
+ executePendingTransactions(mActivityRule, fm);
+
+ assertSame("primary nav fragment not set correctly after replace", strictFragment2,
+ fm.getPrimaryNavigationFragment());
+
+ popBackStackImmediate(mActivityRule, fm);
+
+ assertNull("primary nav fragment not null after popping replace",
+ fm.getPrimaryNavigationFragment());
+ }
+}
diff --git a/tests/fragment/src/android/fragment/cts/StrictFragment.java b/tests/fragment/src/android/fragment/cts/StrictFragment.java
index f552eaa..67ca91e 100644
--- a/tests/fragment/src/android/fragment/cts/StrictFragment.java
+++ b/tests/fragment/src/android/fragment/cts/StrictFragment.java
@@ -100,8 +100,8 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (mCalledOnCreate) {
- throw new IllegalStateException("onCreate called more than once");
+ if (mCalledOnCreate && !mCalledOnDestroy) {
+ throw new IllegalStateException("onCreate called more than once with no onDestroy");
}
mCalledOnCreate = true;
checkState("onCreate", ATTACHED);
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 700aff2..ac38171 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -643,6 +643,24 @@
});
}
+ /**
+ *
+ * This test verifies that custom ValueAnimators will be start()'ed in a set.
+ */
+ @Test
+ public void testChildAnimatorStartCalled() throws Throwable {
+ MyValueAnimator a1 = new MyValueAnimator();
+ MyValueAnimator a2 = new MyValueAnimator();
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(a1, a2);
+ mActivityRule.runOnUiThread(() -> {
+ set.start();
+ assertTrue(a1.mStartCalled);
+ assertTrue(a2.mStartCalled);
+ });
+
+ }
+
static class TargetObj {
public float value = 0;
@@ -668,4 +686,13 @@
mEndIsCalled = true;
}
}
+
+ static class MyValueAnimator extends ValueAnimator {
+ boolean mStartCalled = false;
+ @Override
+ public void start() {
+ // Do not call super intentionally.
+ mStartCalled = true;
+ }
+ }
}
diff --git a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
index 4939f3f..d471212 100644
--- a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
@@ -254,7 +254,7 @@
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(500)).onAnimationEnd(anim);
+ verify(listener, within(500)).onAnimationEnd(anim, false);
// Verify that null target ObjectAnimator didn't get canceled.
verify(listener, times(0)).onAnimationCancel(anim);
// Verify that the update listeners gets called a few times.
@@ -389,7 +389,7 @@
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(400)).onAnimationEnd(anim);
+ verify(listener, within(400)).onAnimationEnd(anim, false);
}
@Test
@@ -508,7 +508,7 @@
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(400)).onAnimationEnd(anim);
+ verify(listener, within(400)).onAnimationEnd(anim, false);
}
@Test
@@ -609,7 +609,7 @@
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(400)).onAnimationEnd(anim);
+ verify(listener, within(400)).onAnimationEnd(anim, false);
}
@Test
diff --git a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
index 3cbbbb2..3b827ec 100644
--- a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
+++ b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
@@ -144,7 +144,7 @@
throws InterruptedException {
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
objectAnimator.addListener(listener);
- verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator);
+ verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator, false);
mInstrumentation.waitForIdleSync();
}
@@ -363,7 +363,7 @@
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(400)).onAnimationEnd(anim);
+ verify(listener, within(400)).onAnimationEnd(anim, false);
}
@Test
@@ -457,7 +457,7 @@
final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
anim.addListener(listener);
mActivityRule.runOnUiThread(anim::start);
- verify(listener, within(400)).onAnimationEnd(anim);
+ verify(listener, within(400)).onAnimationEnd(anim, false);
}
@Test
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index 6094362..59b8e5db 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -23,10 +23,12 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -134,6 +136,25 @@
assertEquals(startDelay, mValueAnimator.getStartDelay());
}
+ /**
+ * Verify that an animator with start delay will have its listener's onAnimationStart(...)
+ * and onAnimationEnd(...) called at the right time.
+ */
+ @Test
+ public void testListenerCallbackWithStartDelay() throws Throwable {
+ final ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setStartDelay(300);
+ anim.setDuration(300);
+ AnimatorListener listener = mock(AnimatorListenerAdapter.class);
+ anim.addListener(listener);
+ mActivityRule.runOnUiThread(() -> {
+ anim.start();
+ });
+
+ verify(listener, timeout(450).times(1)).onAnimationStart(anim, false);
+ verify(listener, timeout(450).times(1)).onAnimationEnd(anim, false);
+ }
+
@Test
public void testGetCurrentPlayTime() throws Throwable {
startAnimation(mValueAnimator);
@@ -195,9 +216,9 @@
currentPlayTime = delayedAnim.getCurrentPlayTime();
currentFraction = delayedAnim.getAnimatedFraction();
currentValue = (Float) delayedAnim.getAnimatedValue();
- assertEquals(proposedCurrentPlayTime, currentPlayTime);
- assertEquals(.5f, currentFraction, EPSILON);
- assertEquals(50, currentValue, EPSILON);
+ assertTrue(currentPlayTime > proposedCurrentPlayTime);
+ assertTrue(currentFraction > 0.5f);
+ assertTrue(currentValue > 50);
mActivityRule.runOnUiThread(delayedAnim::cancel);
}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
index 288a3e9..2396df3 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/ActivityTransitionActivity.java
@@ -29,7 +29,7 @@
import android.transition.Fade;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
-import android.transition.Transition.TransitionListenerAdapter;
+import android.transition.TransitionListenerAdapter;
import android.view.View;
import java.util.List;
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 3f98cb3..362d807 100755
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -53,6 +53,8 @@
/** Lifecycle Test intent constants */
public static final String LIFECYCLE_PREFIX = ACTION_PREFIX + "lifecycle_";
public static final String LIFECYCLE_HASRESUMED = LIFECYCLE_PREFIX + "hasResumed";
+ public static final String LIFECYCLE_HASFOCUS = LIFECYCLE_PREFIX + "hasFocus";
+ public static final String LIFECYCLE_LOSTFOCUS = LIFECYCLE_PREFIX + "lostFocus";
public static final String LIFECYCLE_ONPAUSE = LIFECYCLE_PREFIX + "onpause";
public static final String LIFECYCLE_ONSTOP = LIFECYCLE_PREFIX + "onstop";
public static final String LIFECYCLE_ONDESTROY = LIFECYCLE_PREFIX + "ondestroy";
@@ -84,6 +86,7 @@
public static final String DISABLE_CONTEXT = "DISABLE_CONTEXT";
public static final String FLAG_SECURE = "FLAG_SECURE";
public static final String LIFECYCLE = "LIFECYCLE";
+ public static final String LIFECYCLE_NOUI = "LIFECYCLE_NOUI";
public static final String SCREENSHOT = "SCREENSHOT";
public static final String EXTRA_ASSIST = "EXTRA_ASSIST";
public static final String VERIFY_CONTENT_VIEW = "VERIFY_CONTENT_VIEW";
@@ -95,6 +98,11 @@
/** Session intent constants */
public static final String HIDE_SESSION = "android.intent.action.hide_session";
+ /** Lifecycle activity intent constants */
+ /** Session intent constants */
+ public static final String HIDE_LIFECYCLE_ACTIVITY
+ = "android.intent.action.hide_lifecycle_activity";
+
/** Stub html view to load into WebView */
public static final String WEBVIEW_HTML_GREETING = "Hello WebView!";
public static final String WEBVIEW_HTML = "<html><body><div><p>" + WEBVIEW_HTML_GREETING
@@ -148,6 +156,7 @@
case ASSIST_STRUCTURE:
case FLAG_SECURE:
case LIFECYCLE:
+ case LIFECYCLE_NOUI:
case SCREENSHOT:
case EXTRA_ASSIST:
case VERIFY_CONTENT_VIEW:
@@ -177,6 +186,7 @@
return new ComponentName(
"android.assist.testapp", "android.assist.testapp.SecureActivity");
case LIFECYCLE:
+ case LIFECYCLE_NOUI:
return new ComponentName(
"android.assist.testapp", "android.assist.testapp.LifecycleActivity");
case SCREENSHOT:
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index 354d771..6f01d3d 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -44,6 +44,7 @@
<intent-filter>
<action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
<action android:name="android.intent.action.START_TEST_LIFECYCLE" />
+ <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI" />
<action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
<action android:name="android.intent.action.START_TEST_SCREENSHOT" />
<action android:name="android.intent.action.START_TEST_EXTRA_ASSIST" />
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
index 916d676..708cf9a 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -43,8 +43,15 @@
private CountDownLatch mResumeLatch;
@Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(TAG, "onCreate received");
+ }
+
+ @Override
public void onReady() {
super.onReady();
+ Log.i(TAG, "onReady received");
mReady = true;
}
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 7bca9be..0224498 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -92,6 +92,15 @@
}
@Override
+ public void onPrepareShow(Bundle args, int showFlags) {
+ if (Utils.LIFECYCLE_NOUI.equals(args.getString(Utils.TESTCASE_TYPE, ""))) {
+ setUiEnabled(false);
+ } else {
+ setUiEnabled(true);
+ }
+ }
+
+ @Override
public void onShow(Bundle args, int showFlags) {
if ((showFlags & SHOW_WITH_ASSIST) == 0) {
return;
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index 9f095aa..0b51314 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,22 +16,12 @@
package android.assist.cts;
-import com.android.compatibility.common.util.SystemUtil;
-
-import android.assist.cts.TestStartActivity;
import android.assist.common.Utils;
-import android.app.Activity;
-import android.app.assist.AssistContent;
-import android.app.assist.AssistStructure;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import java.lang.Override;
@@ -43,6 +33,8 @@
public class LifecycleTest extends AssistTestBase {
private static final String TAG = "LifecycleTest";
private static final String action_hasResumed = Utils.LIFECYCLE_HASRESUMED;
+ private static final String action_hasFocus = Utils.LIFECYCLE_HASFOCUS;
+ private static final String action_lostFocus = Utils.LIFECYCLE_LOSTFOCUS;
private static final String action_onPause = Utils.LIFECYCLE_ONPAUSE;
private static final String action_onStop = Utils.LIFECYCLE_ONSTOP;
private static final String action_onDestroy = Utils.LIFECYCLE_ONDESTROY;
@@ -50,39 +42,52 @@
private static final String TEST_CASE_TYPE = Utils.LIFECYCLE;
private BroadcastReceiver mLifecycleTestBroadcastReceiver;
- private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
- private CountDownLatch mActivityLifecycleLatch = new CountDownLatch(1);
- private CountDownLatch mReadyLatch = new CountDownLatch(1);
+ private CountDownLatch mHasResumedLatch;
+ private CountDownLatch mHasFocusLatch;
+ private CountDownLatch mLostFocusLatch;
+ private CountDownLatch mActivityLifecycleLatch;
+ private CountDownLatch mDestroyLatch;
+ private CountDownLatch mReadyLatch;
+ private boolean mLostFocusIsLifecycle;
@Override
public void setUp() throws Exception {
super.setUp();
- setUpAndRegisterReceiver();
- startTestActivity(TEST_CASE_TYPE);
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
- if (mLifecycleTestBroadcastReceiver != null) {
- mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
- mLifecycleTestBroadcastReceiver = null;
- }
- }
-
- private void setUpAndRegisterReceiver() {
- if (mLifecycleTestBroadcastReceiver != null) {
- mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
- }
mLifecycleTestBroadcastReceiver = new LifecycleTestReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(action_hasResumed);
+ filter.addAction(action_hasFocus);
+ filter.addAction(action_lostFocus);
filter.addAction(action_onPause);
filter.addAction(action_onStop);
filter.addAction(action_onDestroy);
filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
mContext.registerReceiver(mLifecycleTestBroadcastReceiver, filter);
+ mHasResumedLatch = new CountDownLatch(1);
+ mHasFocusLatch = new CountDownLatch(1);
+ mLostFocusLatch = new CountDownLatch(1);
+ mActivityLifecycleLatch = new CountDownLatch(1);
+ mDestroyLatch = new CountDownLatch(1);
+ mReadyLatch = new CountDownLatch(1);
+ mLostFocusIsLifecycle = false;
+ startTestActivity(TEST_CASE_TYPE);
+ }
+ @Override
+ public void tearDown() throws Exception {
+ mContext.sendBroadcast(new Intent(Utils.HIDE_LIFECYCLE_ACTIVITY));
+ waitForDestroy();
+ super.tearDown();
+ if (mLifecycleTestBroadcastReceiver != null) {
+ mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
+ mLifecycleTestBroadcastReceiver = null;
+ }
+ mHasResumedLatch = null;
+ mHasFocusLatch = null;
+ mLostFocusLatch = null;
+ mActivityLifecycleLatch = null;
+ mDestroyLatch = null;
+ mReadyLatch = null;
}
private void waitForOnResume() throws Exception {
@@ -92,12 +97,33 @@
}
}
+ private void waitForHasFocus() throws Exception {
+ Log.i(TAG, "waiting for window focus gain before continuing");
+ if (!mHasFocusLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Activity failed to get focus in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+ }
+ }
+
+ private void waitForLostFocus() throws Exception {
+ Log.i(TAG, "waiting for window focus lost before continuing");
+ if (!mLostFocusLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Activity failed to lose focus in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+ }
+ }
+
private void waitAndSeeIfLifecycleMethodsAreTriggered() throws Exception {
if (mActivityLifecycleLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
fail("One or more lifecycle methods were called after triggering assist");
}
}
+ private void waitForDestroy() throws Exception {
+ Log.i(TAG, "waiting for activity destroy before continuing");
+ if (!mDestroyLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Activity failed to destroy in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+ }
+ }
+
public void testLayerDoesNotTriggerLifecycleMethods() throws Exception {
if (mActivityManager.isLowRamDevice()) {
Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -107,8 +133,32 @@
waitForAssistantToBeReady(mReadyLatch);
mTestActivity.start3pApp(Utils.LIFECYCLE);
waitForOnResume();
+ waitForHasFocus();
startSession();
waitForContext();
+ // Since there is no UI, focus should not be lost. We are counting focus lost as
+ // a lifecycle event in this case.
+ // Do this after waitForContext(), since we don't start looking for context until
+ // calling the above (RACY!!!).
+ waitForLostFocus();
+ waitAndSeeIfLifecycleMethodsAreTriggered();
+ }
+
+ public void testNoUiLayerDoesNotTriggerLifecycleMethods() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ Log.d(TAG, "Not running assist tests on low-RAM device.");
+ return;
+ }
+ mLostFocusIsLifecycle = true;
+ mTestActivity.startTest(Utils.LIFECYCLE_NOUI);
+ waitForAssistantToBeReady(mReadyLatch);
+ mTestActivity.start3pApp(Utils.LIFECYCLE_NOUI);
+ waitForOnResume();
+ waitForHasFocus();
+ startSession();
+ waitForContext();
+ // Do this after waitForContext(), since we don't start looking for context until
+ // calling the above (RACY!!!).
waitAndSeeIfLifecycleMethodsAreTriggered();
}
@@ -118,12 +168,21 @@
String action = intent.getAction();
if (action.equals(action_hasResumed) && mHasResumedLatch != null) {
mHasResumedLatch.countDown();
+ } else if (action.equals(action_hasFocus) && mHasFocusLatch != null) {
+ mHasFocusLatch.countDown();
+ } else if (action.equals(action_lostFocus) && mLostFocusLatch != null) {
+ if (mLostFocusIsLifecycle) {
+ mActivityLifecycleLatch.countDown();
+ } else {
+ mLostFocusLatch.countDown();
+ }
} else if (action.equals(action_onPause) && mActivityLifecycleLatch != null) {
mActivityLifecycleLatch.countDown();
} else if (action.equals(action_onStop) && mActivityLifecycleLatch != null) {
mActivityLifecycleLatch.countDown();
} else if (action.equals(action_onDestroy) && mActivityLifecycleLatch != null) {
mActivityLifecycleLatch.countDown();
+ mDestroyLatch.countDown();
} else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
if (mReadyLatch != null) {
mReadyLatch.countDown();
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
index af10f99..0602d3f 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
@@ -17,17 +17,34 @@
package android.assist.testapp;
import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
public class LifecycleActivity extends Activity {
private static final String TAG = "LifecycleActivity";
+ private BroadcastReceiver mReceiver;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "LifecycleActivity created");
+ mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals("android.intent.action.hide_lifecycle_activity")) {
+ finish();
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction("android.intent.action.hide_lifecycle_activity");
+ registerReceiver(mReceiver, filter);
}
@Override
@@ -38,6 +55,17 @@
}
@Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.i(TAG, "Activity focus changed: " + hasFocus);
+ if (hasFocus) {
+ sendBroadcast(new Intent("android.intent.action.lifecycle_hasFocus"));
+ } else {
+ sendBroadcast(new Intent("android.intent.action.lifecycle_lostFocus"));
+ }
+ }
+
+ @Override
protected void onPause() {
Log.i(TAG, "activity was paused");
sendBroadcast(new Intent("android.intent.action.lifecycle_onpause"));
@@ -55,6 +83,7 @@
protected void onDestroy() {
Log.i(TAG, "activity was destroyed");
sendBroadcast(new Intent("android.intent.action.lifecycle_ondestroy"));
+ unregisterReceiver(mReceiver);
super.onDestroy();
}
}
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 6c2d546..545a6b7 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -10,11 +10,15 @@
# TODO(dimitry): Can this list be constructed dynamically?
my_bionic_testlib_files := \
+ cfi_test_helper/cfi_test_helper \
+ cfi_test_helper2/cfi_test_helper2 \
dt_runpath_a/libtest_dt_runpath_a.so \
dt_runpath_b_c_x/libtest_dt_runpath_b.so \
dt_runpath_b_c_x/libtest_dt_runpath_c.so \
dt_runpath_b_c_x/libtest_dt_runpath_x.so \
libatest_simple_zip/libatest_simple_zip.so \
+ libcfi-test.so \
+ libcfi-test-bad.so \
libdlext_test_different_soname.so \
libdlext_test_fd/libdlext_test_fd.so \
libdlext_test_norelro.so \
diff --git a/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java b/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
index 2bea43f..c52aba6 100644
--- a/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
+++ b/tests/tests/contactsproviderwipe/src/android/provider/cts/contactsproviderwipe/ContactsContract_Wipe.java
@@ -16,6 +16,10 @@
package android.provider.cts.contactsproviderwipe;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
@@ -25,6 +29,7 @@
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.ProviderStatus;
import android.support.test.InstrumentationRegistry;
import android.test.AndroidTestCase;
@@ -157,11 +162,11 @@
assertBigger(newTimestamp, start);
}
- private void checkDatabaseWipeNotification(Uri notificationUri) throws Exception {
+ public void testDatabaseWipeNotification() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Uri> notifiedUri = new AtomicReference<>();
- getContext().getContentResolver().registerContentObserver(notificationUri,
+ getContext().getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
/* notifyForDescendants=*/ false,
new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
@@ -177,11 +182,26 @@
assertTrue("Didn't receive content change notification",
latch.await(60, TimeUnit.SECONDS));
- assertEquals(notificationUri, notifiedUri.get());
+ assertEquals(ProviderStatus.CONTENT_URI, notifiedUri.get());
}
- public void testDatabaseWipeNotification() throws Exception {
- checkDatabaseWipeNotification(ProviderStatus.CONTENT_URI);
- checkDatabaseWipeNotification(ProviderStatus.STATUS_CHANGE_NOTIFICATION_CONTENT_URI);
+ public void testDatabaseWipeBroadcast() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intents.CONTACTS_DATABASE_CREATED);
+
+ getContext().registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received broadcast: " + intent);
+ latch.countDown();
+ }
+ }, filter);
+
+ wipeContactsProvider();
+
+ assertTrue("Didn't receive contacts wipe broadcast",
+ latch.await(60, TimeUnit.SECONDS));
}
}
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index 684e665..caaa669 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -20,6 +20,7 @@
LOCAL_MODULE_TAGS := optional
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_USE_AAPT2 := true
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/tests/content/res/font/samplefont.ttf b/tests/tests/content/res/font/samplefont.ttf
new file mode 100644
index 0000000..49f1c62
--- /dev/null
+++ b/tests/tests/content/res/font/samplefont.ttf
Binary files differ
diff --git a/tests/tests/content/res/font/samplexmlfont.xml b/tests/tests/content/res/font/samplexmlfont.xml
new file mode 100644
index 0000000..2905c13
--- /dev/null
+++ b/tests/tests/content/res/font/samplexmlfont.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/content/res/values/styles.xml b/tests/tests/content/res/values/styles.xml
index c6e4b1d..d1c30d7 100644
--- a/tests/tests/content/res/values/styles.xml
+++ b/tests/tests/content/res/values/styles.xml
@@ -128,8 +128,8 @@
<style name="TestProgressBar">
<item name="android:indeterminateOnly">false</item>
- <item name="android:progressDrawable">?android:drawable/progress_horizontal</item>
- <item name="android:indeterminateDrawable">?android:drawable/progress_horizontal</item>
+ <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
+ <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:focusable">true</item>
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
new file mode 100644
index 0000000..a69a3f8
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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 android.content.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipDescriptionTest {
+ @UiThreadTest
+ @Test
+ public void testGetTimestamp() {
+ final ClipboardManager clipboardManager = (ClipboardManager)
+ InstrumentationRegistry.getTargetContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ final long timestampBeforeSet = SystemClock.elapsedRealtime();
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("Dummy text", "Text"));
+ final long timestampAfterSet = SystemClock.elapsedRealtime();
+ final long timestamp = clipboardManager.getPrimaryClipDescription().getTimestamp();
+ if (timestamp < timestampBeforeSet || timestamp > timestampAfterSet) {
+ fail("Value of timestamp is not as expected.\n"
+ + "timestamp before setting clip: " + timestampBeforeSet + "\n"
+ + "timestamp after setting clip: " + timestampAfterSet + "\n"
+ + "actual timestamp: " + timestamp
+ + "clipdata: " + clipboardManager.getPrimaryClip());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 18b1fd3..917f184 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -31,6 +31,7 @@
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -706,4 +707,41 @@
is.close();
}
+
+ public void testGetFont_invalidResourceId() {
+ try {
+ mResources.getFont(-1);
+ fail("Font resource -1 should not be found.");
+ } catch (NotFoundException e) {
+ //expected
+ }
+ }
+
+ public void testGetFont_fontFile() {
+ Typeface font = mResources.getFont(R.font.samplefont);
+
+ assertNotNull(font);
+ assertNotSame(Typeface.DEFAULT, font);
+ }
+
+ public void testGetFont_xmlFile() {
+ Typeface font = mResources.getFont(R.font.samplexmlfont);
+
+ assertNotNull(font);
+ assertNotSame(Typeface.DEFAULT, font);
+ }
+
+ public void testGetFont_fontFileIsCached() {
+ Typeface font = mResources.getFont(R.font.samplefont);
+ Typeface font2 = mResources.getFont(R.font.samplefont);
+
+ assertEquals(font, font2);
+ }
+
+ public void testGetFont_xmlFileIsCached() {
+ Typeface font = mResources.getFont(R.font.samplexmlfont);
+ Typeface font2 = mResources.getFont(R.font.samplexmlfont);
+
+ assertEquals(font, font2);
+ }
}
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
index f508857..28e9236 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
index fc383fb..b27bdbd 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
index 7afe0da..c9677a6 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
index 801573c..8882a7a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
index 899a235..143ce3e 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
index ba6d8c7..cdae7fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
index c57ad20e..2bf7882 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 4916ffa..15a3a43 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -900,6 +900,21 @@
}
@Test
+ public void testSetGetFontVariationSettings() {
+ Paint p = new Paint();
+
+ // The default variation settings should be null.
+ assertNull(p.getFontVariationSettings());
+
+ final String settings = "'wdth' 1.0";
+ p.setFontVariationSettings(settings);
+ assertEquals(settings, p.getFontVariationSettings());
+
+ p.setFontVariationSettings("");
+ assertNull(p.getFontVariationSettings());
+ }
+
+ @Test
public void testGetTextBounds() {
Paint p = new Paint();
p.setTextSize(10);
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index 531549e..21e9086 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -59,6 +59,7 @@
private PackageManager mPm;
private FeatureInfo mVulkanHardwareLevel = null;
private FeatureInfo mVulkanHardwareVersion = null;
+ private FeatureInfo mVulkanHardwareCompute = null;
private JSONObject mVulkanDevices[];
@Before
@@ -77,6 +78,11 @@
if (DEBUG) {
Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version));
}
+ } else if (PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE.equals(feature.name)) {
+ mVulkanHardwareCompute = feature;
+ if (DEBUG) {
+ Log.d(TAG, feature.name + "=" + feature.version);
+ }
}
}
}
@@ -96,6 +102,9 @@
assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_VERSION +
" is supported, but no Vulkan physical devices are available",
mVulkanHardwareLevel);
+ assertNull("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+ " is supported, but no Vulkan physical devices are available",
+ mVulkanHardwareCompute);
return;
}
assertNotNull("Vulkan physical devices are available, but system feature " +
@@ -116,20 +125,31 @@
" version 0x" + Integer.toHexString(mVulkanHardwareVersion.version) + " is not" +
" one of the versions allowed",
isHardwareVersionAllowed(mVulkanHardwareVersion.version));
+ if (mVulkanHardwareCompute != null) {
+ assertTrue("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+ " version " + mVulkanHardwareCompute.version +
+ " is not one of the versions allowed",
+ mVulkanHardwareCompute.version == 0);
+ }
JSONObject bestDevice = null;
int bestDeviceLevel = -1;
+ int bestComputeLevel = -1;
int bestDeviceVersion = -1;
for (JSONObject device : mVulkanDevices) {
int level = determineHardwareLevel(device);
+ int compute = determineHardwareCompute(device);
int version = determineHardwareVersion(device);
if (DEBUG) {
Log.d(TAG, device.getJSONObject("properties").getString("deviceName") +
- ": level=" + level + " version=0x" + Integer.toHexString(version));
+ ": level=" + level + " compute=" + compute +
+ " version=0x" + Integer.toHexString(version));
}
- if (level >= bestDeviceLevel && version >= bestDeviceVersion) {
+ if (level >= bestDeviceLevel && compute >= bestComputeLevel &&
+ version >= bestDeviceVersion) {
bestDevice = device;
bestDeviceLevel = level;
+ bestComputeLevel = compute;
bestDeviceVersion = version;
}
}
@@ -144,6 +164,16 @@
" isn't close enough (same major and minor version, less or equal patch version)" +
" to best physical device version 0x" + Integer.toHexString(bestDeviceVersion),
isVersionCompatible(bestDeviceVersion, mVulkanHardwareVersion.version));
+ if (mVulkanHardwareCompute == null) {
+ assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+ " not present, but required features are supported",
+ bestComputeLevel, -1);
+ } else {
+ assertEquals("System feature " + PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE +
+ " version " + mVulkanHardwareCompute.version +
+ " doesn't match best physical device hardware compute " + bestComputeLevel,
+ bestComputeLevel, mVulkanHardwareCompute.version);
+ }
}
@Test
@@ -190,6 +220,23 @@
return 1;
}
+ private int determineHardwareCompute(JSONObject device) throws JSONException {
+ boolean have16bitStorage = false;
+ boolean haveVariablePointers = false;
+ JSONArray extensions = device.getJSONArray("extensions");
+ for (int i = 0; i < extensions.length(); i++) {
+ String name = extensions.getJSONObject(i).getString("extensionName");
+ if (name.equals("VK_KHR_16bit_storage"))
+ have16bitStorage = true;
+ else if (name.equals("VK_KHR_variable_pointers"))
+ haveVariablePointers = true;
+ }
+ if (!have16bitStorage || !haveVariablePointers) {
+ return -1;
+ }
+ return 0;
+ }
+
private int determineHardwareVersion(JSONObject device) throws JSONException {
return device.getJSONObject("properties").getInt("apiVersion");
}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
index ee24eed..2392249 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
@@ -16,6 +16,7 @@
package android.keystore.cts;
+import android.security.GateKeeper;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.test.MoreAsserts;
@@ -47,6 +48,7 @@
MoreAsserts.assertEmpty(Arrays.asList(spec.getSignaturePaddings()));
assertFalse(spec.isUserAuthenticationRequired());
assertEquals(-1, spec.getUserAuthenticationValidityDurationSeconds());
+ assertEquals(GateKeeper.INVALID_SECURE_USER_ID, spec.getBoundToSpecificSecureUserId());
}
public void testSettersReflectedInGetters() {
@@ -70,6 +72,7 @@
KeyProperties.SIGNATURE_PADDING_RSA_PSS)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(123456)
+ .setBoundToSpecificSecureUserId(654321)
.build();
assertEquals(
@@ -89,6 +92,7 @@
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS);
assertTrue(spec.isUserAuthenticationRequired());
assertEquals(123456, spec.getUserAuthenticationValidityDurationSeconds());
+ assertEquals(654321, spec.getBoundToSpecificSecureUserId());
}
public void testSetKeyValidityEndDateAppliesToBothEndDates() {
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index f16cadd..5993a95 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -499,6 +499,7 @@
result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
result.setUserAuthenticationValidityDurationSeconds(
spec.getUserAuthenticationValidityDurationSeconds());
+ result.setBoundToSpecificSecureUserId(spec.getBoundToSpecificSecureUserId());
return result;
}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index cae1611..9dc6943 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -422,7 +422,7 @@
}
public void testVolumeDndAffectedStream() throws Exception {
- if (mUseFixedVolume || mHasVibrator) {
+ if (mUseFixedVolume || mHasVibrator || mIsTelevision) {
return;
}
Utils.toggleNotificationPolicyAccess(
@@ -634,7 +634,7 @@
}
public void testMuteDndAffectedStreams() throws Exception {
- if (mUseFixedVolume) {
+ if (mUseFixedVolume || mIsTelevision) {
return;
}
int[] streams = { AudioManager.STREAM_RING };
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
index 7c81816..9de3bd2 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
@@ -45,9 +45,7 @@
public class AudioTrackSurroundTest extends CtsAndroidTestCase {
private static final String TAG = "AudioTrackSurroundTest";
- // We typically find tolerance to be within 0.2 percent, but we allow one percent.
private static final double MAX_RATE_TOLERANCE_FRACTION = 0.01;
- private static final double MAX_INSTANTANEOUS_RATE_TOLERANCE_FRACTION = 0.15;
private static final boolean LOG_TIMESTAMPS = false; // set true for debugging
// Set this true to prefer the device that supports the particular encoding.
@@ -237,31 +235,54 @@
void checkIndividualTimestamps(int sampleRate) {
AudioTimestamp previous = null;
+ double sumDeltaSquared = 0.0;
+ int populationSize = 0;
+ double maxDeltaMillis = 0.0;
// Make sure the timestamps are smooth and don't go retrograde.
for (AudioTimestamp timestamp : mTimestamps) {
if (previous != null) {
- final double TOLERANCE_MILLIS = 2.0;
- assertTrue("framePosition should be monotonic",
+ assertTrue("framePosition must be monotonic",
timestamp.framePosition >= previous.framePosition);
- assertTrue("nanoTime should be monotonic",
+ assertTrue("nanoTime must be monotonic",
timestamp.nanoTime >= previous.nanoTime);
if (timestamp.framePosition > previous.framePosition) {
+ // Measure timing jitter.
// Calculate predicted duration based on measured rate and compare
// it with actual duration.
+ final double TOLERANCE_MILLIS = 2.0;
long elapsedFrames = timestamp.framePosition - previous.framePosition;
long elapsedNanos = timestamp.nanoTime - previous.nanoTime;
- double expectedNanos = elapsedFrames * (double) NANOS_PER_SECOND / sampleRate;
- assertEquals("elapsed time should match predicted duration"
- + ", sampleRate = " + sampleRate
- + ", framePosition = " + timestamp.framePosition,
- expectedNanos, (double) elapsedNanos,
- TOLERANCE_MILLIS * NANOS_PER_MILLISECOND);
+ double measuredMillis = elapsedNanos / (double) NANOS_PER_MILLISECOND;
+ double expectedMillis = elapsedFrames * (double) MILLIS_PER_SECOND
+ / sampleRate;
+ double deltaMillis = measuredMillis - expectedMillis;
+ sumDeltaSquared += deltaMillis * deltaMillis;
+ populationSize++;
+ // We only issue a warning here because the CDD does not mandate a
+ // specific tolerance.
+ double absDeltaMillis = Math.abs(deltaMillis);
+ if (absDeltaMillis > TOLERANCE_MILLIS) {
+ Log.w(TAG, "measured time exceeds expected"
+ + ", srate = " + sampleRate
+ + ", frame = " + timestamp.framePosition
+ + ", expected = " + expectedMillis
+ + ", measured = " + measuredMillis + " (msec)"
+ );
+ }
+ if (absDeltaMillis > maxDeltaMillis) {
+ maxDeltaMillis = absDeltaMillis;
+ }
}
}
previous = timestamp;
}
+ Log.d(TAG, "max abs(delta) from expected duration = " + maxDeltaMillis + " msec");
+ if (populationSize > 0) {
+ double deviation = Math.sqrt(sumDeltaSquared / populationSize);
+ Log.d(TAG, "standard deviation from expected duration = " + deviation + " msec");
+ }
}
// Use collected timestamps to estimate a sample rate.
@@ -414,10 +435,11 @@
// Estimate the sample rate and compare it with expected.
double estimatedRate = mTimestampAnalyzer.estimateSampleRate();
+ Log.d(TAG, "measured sample rate = " + estimatedRate);
assertEquals(TEST_NAME + ": measured sample rate" + getPcmWarning(),
mSampleRate, estimatedRate, mSampleRate * MAX_RATE_TOLERANCE_FRACTION);
- // Check for jitter of retrograde motion in each timestamp.
+ // Check for jitter or retrograde motion in each timestamp.
mTimestampAnalyzer.checkIndividualTimestamps(mSampleRate);
} finally {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 13484b2..3d853d4 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -373,6 +373,43 @@
aa.getContentType());
}
+ // Test case 5: build AudioTrack with attributes and performance mode
+ public void testBuilderAttributesPerformanceMode() throws Exception {
+ // constants for test
+ final String TEST_NAME = "testBuilderAttributesPerformanceMode";
+ final int testPerformanceModes[] = new int[] {
+ AudioTrack.PERFORMANCE_MODE_NONE,
+ AudioTrack.PERFORMANCE_MODE_LOW_LATENCY,
+ AudioTrack.PERFORMANCE_MODE_POWER_SAVING,
+ };
+ // construct various attributes with different preset performance modes.
+ final AudioAttributes testAttributes[] = new AudioAttributes[] {
+ new AudioAttributes.Builder().build(),
+ new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_LOW_LATENCY).build(),
+ new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_DEEP_BUFFER).build(),
+ };
+ for (int performanceMode : testPerformanceModes) {
+ for (AudioAttributes attributes : testAttributes) {
+ final AudioTrack track = new AudioTrack.Builder()
+ .setPerformanceMode(performanceMode)
+ .setAudioAttributes(attributes)
+ .build();
+ // save results
+ final int actualPerformanceMode = track.getPerformanceMode();
+ // release track before the test exits
+ track.release();
+ final String result = "Attribute flags: " + attributes.getAllFlags()
+ + " set performance mode: " + performanceMode
+ + " actual performance mode: " + actualPerformanceMode;
+ Log.d(TEST_NAME, result);
+ assertTrue(TEST_NAME + ": " + result,
+ actualPerformanceMode == performanceMode // either successful
+ || actualPerformanceMode == AudioTrack.PERFORMANCE_MODE_NONE // or none
+ || performanceMode == AudioTrack.PERFORMANCE_MODE_NONE);
+ }
+ }
+ }
+
// -----------------------------------------------------------------
// Playback head position
// ----------------------------------
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index 9ec4956..e13bba6 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -19,8 +19,10 @@
import android.media.AudioManager;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.MediaDescription;
import android.media.session.MediaController;
import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
import android.media.session.PlaybackState;
import android.media.session.PlaybackState.CustomAction;
import android.net.Uri;
@@ -51,6 +53,7 @@
super.setUp();
mSession = new MediaSession(getContext(), SESSION_TAG);
mSession.setCallback(mCallback, mHandler);
+ mSession.setFlags(MediaSession.FLAG_HANDLES_QUEUE_COMMANDS);
mController = mSession.getController();
}
@@ -85,6 +88,44 @@
}
}
+ public void testAddRemoveQueueItems() throws Exception {
+ final String mediaId = "media_id";
+ final String mediaTitle = "media_title";
+ MediaDescription itemDescription = new MediaDescription.Builder()
+ .setMediaId(mediaId).setTitle(mediaTitle).build();
+
+ synchronized (mWaitLock) {
+ mCallback.reset();
+ mController.addQueueItem(itemDescription);
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnAddQueueItemCalled);
+ assertEquals(-1, mCallback.mQueueIndex);
+ assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+ assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+
+ mCallback.reset();
+ mController.addQueueItem(itemDescription, 0);
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnAddQueueItemAtCalled);
+ assertEquals(0, mCallback.mQueueIndex);
+ assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+ assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+
+ mCallback.reset();
+ mController.removeQueueItemAt(0);
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnRemoveQueueItemAtCalled);
+ assertEquals(0, mCallback.mQueueIndex);
+
+ mCallback.reset();
+ mController.removeQueueItem(itemDescription);
+ mWaitLock.wait(TIME_OUT_MS);
+ assertTrue(mCallback.mOnRemoveQueueItemCalled);
+ assertEquals(mediaId, mCallback.mQueueDescription.getMediaId());
+ assertEquals(mediaTitle, mCallback.mQueueDescription.getTitle());
+ }
+ }
+
public void testVolumeControl() throws Exception {
VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
@Override
@@ -295,6 +336,8 @@
private class MediaSessionCallback extends MediaSession.Callback {
private long mSeekPosition;
private long mQueueItemId;
+ private int mQueueIndex;
+ private MediaDescription mQueueDescription;
private Rating mRating;
private String mMediaId;
private String mQuery;
@@ -327,10 +370,16 @@
private boolean mOnPrepareFromUriCalled;
private boolean mOnSetRepeatModeCalled;
private boolean mOnSetShuffleModeEnabledCalled;
+ private boolean mOnAddQueueItemCalled;
+ private boolean mOnAddQueueItemAtCalled;
+ private boolean mOnRemoveQueueItemCalled;
+ private boolean mOnRemoveQueueItemAtCalled;
public void reset() {
mSeekPosition = -1;
mQueueItemId = -1;
+ mQueueIndex = -1;
+ mQueueDescription = null;
mRating = null;
mMediaId = null;
mQuery = null;
@@ -363,6 +412,10 @@
mOnPrepareFromUriCalled = false;
mOnSetRepeatModeCalled = false;
mOnSetShuffleModeEnabledCalled = false;
+ mOnAddQueueItemCalled = false;
+ mOnAddQueueItemAtCalled = false;
+ mOnRemoveQueueItemCalled = false;
+ mOnRemoveQueueItemAtCalled = false;
}
@Override
@@ -554,5 +607,42 @@
mWaitLock.notify();
}
}
+
+ @Override
+ public void onAddQueueItem(MediaDescription description) {
+ synchronized (mWaitLock) {
+ mOnAddQueueItemCalled = true;
+ mQueueDescription = description;
+ mWaitLock.notify();
+ }
+ }
+
+ @Override
+ public void onAddQueueItem(MediaDescription description, int index) {
+ synchronized (mWaitLock) {
+ mOnAddQueueItemAtCalled = true;
+ mQueueIndex = index;
+ mQueueDescription = description;
+ mWaitLock.notify();
+ }
+ }
+
+ @Override
+ public void onRemoveQueueItem(MediaDescription description) {
+ synchronized (mWaitLock) {
+ mOnRemoveQueueItemCalled = true;
+ mQueueDescription = description;
+ mWaitLock.notify();
+ }
+ }
+
+ @Override
+ public void onRemoveQueueItemAt(int index) {
+ synchronized (mWaitLock) {
+ mOnRemoveQueueItemAtCalled = true;
+ mQueueIndex = index;
+ mWaitLock.notify();
+ }
+ }
}
}
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index c460519..0bbbe95 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -60,28 +60,28 @@
int source = R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz;
String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90);
+ cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualVideoTrack() throws Exception {
int source = R.raw.video_176x144_h264_408kbps_30fps_352x288_h264_122kbps_30fps;
String outputFile = File.createTempFile("MediaMuxerTest_testDualVideo", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90);
+ cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualAudioTrack() throws Exception {
int source = R.raw.audio_aac_mono_70kbs_44100hz_aac_mono_70kbs_44100hz;
String outputFile = File.createTempFile("MediaMuxerTest_testDualAudio", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 2, 90);
+ cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void testDualVideoAndAudioTrack() throws Exception {
int source = R.raw.video_h264_30fps_video_h264_30fps_aac_44100hz_aac_44100hz;
String outputFile = File.createTempFile("MediaMuxerTest_testDualVideoAudio", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 4, 90);
+ cloneAndVerify(source, outputFile, 4, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -92,7 +92,7 @@
R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro;
String outputFile = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 3, 90);
+ cloneAndVerify(source, outputFile, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -102,7 +102,7 @@
int source = R.raw.sinesweepm4a;
String outputFile = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 1, -1);
+ cloneAndVerify(source, outputFile, 1, -1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
/**
@@ -112,7 +112,21 @@
int source = R.raw.video_only_176x144_3gp_h263_25fps;
String outputFile = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4")
.getAbsolutePath();
- cloneAndVerify(source, outputFile, 1, 180);
+ cloneAndVerify(source, outputFile, 1, 180, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ }
+
+ public void testWebmOutput() throws Exception {
+ int source = R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz;
+ String outputFile = File.createTempFile("testWebmOutput", ".webm")
+ .getAbsolutePath();
+ cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+ }
+
+ public void testThreegppOutput() throws Exception {
+ int source = R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz;
+ String outputFile = File.createTempFile("testThreegppOutput", ".3gp")
+ .getAbsolutePath();
+ cloneAndVerify(source, outputFile, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
}
/**
@@ -245,7 +259,7 @@
* Using the MediaMuxer to clone a media file.
*/
private void cloneMediaUsingMuxer(int srcMedia, String dstMediaPath,
- int expectedTrackCount, int degrees) throws IOException {
+ int expectedTrackCount, int degrees, int fmt) throws IOException {
// Set up MediaExtractor to read from the source.
AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMedia);
MediaExtractor extractor = new MediaExtractor();
@@ -257,7 +271,7 @@
// Set up MediaMuxer for the destination.
MediaMuxer muxer;
- muxer = new MediaMuxer(dstMediaPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ muxer = new MediaMuxer(dstMediaPath, fmt);
// Set up the tracks.
HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
@@ -281,23 +295,26 @@
muxer.setOrientationHint(degrees);
}
- // Test setLocation out of bound cases
- try {
- muxer.setLocation(BAD_LATITUDE, LONGITUDE);
- fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
+ if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+ fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+ // Test setLocation out of bound cases
+ try {
+ muxer.setLocation(BAD_LATITUDE, LONGITUDE);
+ fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
+ "]");
- } catch (IllegalArgumentException e) {
- // Expected
- }
- try {
- muxer.setLocation(LATITUDE, BAD_LONGITUDE);
- fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ try {
+ muxer.setLocation(LATITUDE, BAD_LONGITUDE);
+ fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
+ "]");
- } catch (IllegalArgumentException e) {
- // Expected
- }
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
- muxer.setLocation(LATITUDE, LONGITUDE);
+ muxer.setLocation(LATITUDE, LONGITUDE);
+ }
muxer.start();
while (!sawEOS) {
@@ -341,11 +358,15 @@
* sure they match.
*/
private void cloneAndVerify(int srcMedia, String outputMediaFile,
- int expectedTrackCount, int degrees) throws IOException {
+ int expectedTrackCount, int degrees, int fmt) throws IOException {
try {
- cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount, degrees);
- verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
- verifyLocationInFile(outputMediaFile);
+ cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
+ degrees, fmt);
+ if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+ fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+ verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
+ verifyLocationInFile(outputMediaFile);
+ }
// Check the sample on 1s and 0.5s.
verifySamplesMatch(srcMedia, outputMediaFile, 1000000);
verifySamplesMatch(srcMedia, outputMediaFile, 500000);
diff --git a/tests/tests/nativehardware/Android.mk b/tests/tests/nativehardware/Android.mk
index 3abaccb..d31af82 100644
--- a/tests/tests/nativehardware/Android.mk
+++ b/tests/tests/nativehardware/Android.mk
@@ -23,21 +23,20 @@
LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-#LOCAL_C_INCLUDES := \
-# $(call include-path-for, wilhelm) \
-# $(call include-path-for, wilhelm-ut)
-
LOCAL_SRC_FILES := \
- src/AHardwareBufferTest.cpp
+ src/AHardwareBufferTest.cpp \
+ src/Gralloc1MapperTest.cpp
LOCAL_SHARED_LIBRARIES := \
libandroid \
libandroid_runtime \
+ libui \
libutils \
liblog \
LOCAL_STATIC_LIBRARIES := \
libgtest \
+ libgtest_main \
LOCAL_CTS_TEST_PACKAGE := android.nativehardware
# Tag this module as a cts test artifact
diff --git a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
index f851d87..1f31e5e 100644
--- a/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
+++ b/tests/tests/nativehardware/src/AHardwareBufferTest.cpp
@@ -61,12 +61,13 @@
if (static_cast<uint32_t>(buffer->layerCount) != desc.layers)
return BuildFailureMessage(desc.layers,
static_cast<uint32_t>(buffer->layerCount), "layers");
- if (static_cast<uint32_t>(buffer->usage) !=
- android_hardware_HardwareBuffer_convertToGrallocUsageBits(
- desc.usage0, desc.usage1))
- return BuildFailureMessage(
- android_hardware_HardwareBuffer_convertToGrallocUsageBits(
- desc.usage0, desc.usage1),
+ uint64_t producerUsage = 0;
+ uint64_t consumerUsage = 0;
+ android_hardware_HardwareBuffer_convertToGrallocUsageBits(
+ desc.usage0, desc.usage1, &producerUsage, &consumerUsage);
+ uint64_t combinedUsage = producerUsage | consumerUsage;
+ if (static_cast<uint32_t>(buffer->usage) != combinedUsage)
+ return BuildFailureMessage(combinedUsage,
static_cast<uint32_t>(buffer->usage), "usages");
if (android_hardware_HardwareBuffer_convertFromPixelFormat(
buffer->getPixelFormat()) != desc.format)
@@ -298,8 +299,3 @@
AHardwareBuffer_release(buffer);
}
-
-int main(int argc, char **argv) {
- testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
diff --git a/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp b/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp
new file mode 100644
index 0000000..b7c9f0f
--- /dev/null
+++ b/tests/tests/nativehardware/src/Gralloc1MapperTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "Gralloc1Mapper_test"
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ui/GraphicBuffer.h>
+#include <ui/GraphicBufferMapper.h>
+#include <utils/Errors.h>
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+class Gralloc1MapperTest : public ::testing::Test
+{
+public:
+ ~Gralloc1MapperTest() override = default;
+
+protected:
+ void SetUp() override {
+ buffer = new GraphicBuffer(4, 8, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+ GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN,
+ GRALLOC1_CONSUMER_USAGE_CPU_READ_OFTEN, "Gralloc1MapperTest");
+ ASSERT_NE(nullptr, buffer.get());
+
+ handle = static_cast<buffer_handle_t>(buffer->handle);
+
+ mapper = &GraphicBufferMapper::get();
+ }
+
+ sp<GraphicBuffer> buffer;
+ buffer_handle_t handle;
+ GraphicBufferMapper* mapper;
+};
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getDimensions) {
+ uint32_t width = 0;
+ uint32_t height = 0;
+ status_t err = mapper->getDimensions(handle, &width, &height);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+ EXPECT_EQ(4U, width);
+ EXPECT_EQ(8U, height);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getFormat) {
+ int32_t value = 0;
+ status_t err = mapper->getFormat(handle, &value);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+ EXPECT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getLayerCount) {
+ uint32_t value = 0;
+ status_t err = mapper->getLayerCount(handle, &value);
+ if (err != GRALLOC1_ERROR_UNSUPPORTED) {
+ EXPECT_EQ(1U, value);
+ }
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getProducerUsage) {
+ uint64_t value = 0;
+ status_t err = mapper->getProducerUsage(handle, &value);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+ EXPECT_EQ(GRALLOC1_PRODUCER_USAGE_CPU_WRITE_OFTEN, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getConsumerUsage) {
+ uint64_t value = 0;
+ status_t err = mapper->getConsumerUsage(handle, &value);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+ EXPECT_EQ(GRALLOC1_CONSUMER_USAGE_CPU_READ_OFTEN, value);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getBackingStore) {
+ uint64_t value = 0;
+ status_t err = mapper->getBackingStore(handle, &value);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+}
+
+TEST_F(Gralloc1MapperTest, Gralloc1MapperTest_getStride) {
+ uint32_t value = 0;
+ status_t err = mapper->getStride(handle, &value);
+ ASSERT_EQ(GRALLOC1_ERROR_NONE, err);
+ // The stride should be at least the width of the buffer.
+ EXPECT_LE(4U, value);
+}
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
index f6cdadb..894c2b8 100644
--- a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
@@ -20,167 +20,167 @@
#include <gtest/gtest.h>
#include <utils/Log.h>
-#include <oboe/OboeAudio.h>
-#include <oboe/OboeDefinitions.h>
+#include <aaudio/AAudio.h>
+#include <aaudio/AAudioDefinitions.h>
-// Note that this "Oboe" audio API is in the process of being renamed "AAudio".
+// Note that this "AAudio" audio API is in the process of being renamed "AAudio".
// You may see both names until the conversion is complete.
-// TODO Remove this comment when Oboe has become AAudio.
+// TODO Remove this comment when AAudio has become AAudio.
-#define DEFAULT_STATE_TIMEOUT (500 * OBOE_NANOS_PER_MILLISECOND)
+#define DEFAULT_STATE_TIMEOUT (500 * AAUDIO_NANOS_PER_MILLISECOND)
-// Test OboeStreamBuilder
-TEST(test_aaudio, oboe_stream_builder) {
- const oboe_sample_rate_t requestedSampleRate1 = 48000;
- const oboe_sample_rate_t requestedSampleRate2 = 44100;
+// Test AAudioStreamBuilder
+TEST(test_aaudio, aaudio_stream_builder) {
+ const aaudio_sample_rate_t requestedSampleRate1 = 48000;
+ const aaudio_sample_rate_t requestedSampleRate2 = 44100;
const int32_t requestedSamplesPerFrame = 2;
- const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+ const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
- oboe_sample_rate_t sampleRate = -1;
+ aaudio_sample_rate_t sampleRate = -1;
int32_t samplesPerFrame = -1;
- oboe_audio_format_t actualDataFormat;
- OboeStreamBuilder oboeBuilder1;
- OboeStreamBuilder oboeBuilder2;
+ aaudio_audio_format_t actualDataFormat;
+ AAudioStreamBuilder aaudioBuilder1;
+ AAudioStreamBuilder aaudioBuilder2;
- // Use an OboeStreamBuilder to define the stream.
- oboe_result_t result = Oboe_createStreamBuilder(&oboeBuilder1);
- ASSERT_EQ(OBOE_OK, result);
+ // Use an AAudioStreamBuilder to define the stream.
+ aaudio_result_t result = AAudio_createStreamBuilder(&aaudioBuilder1);
+ ASSERT_EQ(AAUDIO_OK, result);
// Request stream properties.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder1, requestedSampleRate1));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder1,
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder1, requestedSampleRate1));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder1,
requestedSamplesPerFrame));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder1, requestedDataFormat));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder1, requestedDataFormat));
// Check to make sure builder saved the properties.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
EXPECT_EQ(requestedSampleRate1, sampleRate);
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSamplesPerFrame(oboeBuilder1, &samplesPerFrame));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSamplesPerFrame(aaudioBuilder1, &samplesPerFrame));
EXPECT_EQ(requestedSamplesPerFrame, samplesPerFrame);
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getFormat(oboeBuilder1, &actualDataFormat));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getFormat(aaudioBuilder1, &actualDataFormat));
EXPECT_EQ(requestedDataFormat, actualDataFormat);
- result = OboeStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
- EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+ result = AAudioStreamBuilder_getSampleRate(0x0BADCAFE, &sampleRate); // ridiculous token
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
// Create a second builder and make sure they do not collide.
- ASSERT_EQ(OBOE_OK, Oboe_createStreamBuilder(&oboeBuilder2));
- ASSERT_NE(oboeBuilder1, oboeBuilder2);
+ ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder2));
+ ASSERT_NE(aaudioBuilder1, aaudioBuilder2);
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder2, requestedSampleRate2));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder2, requestedSampleRate2));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate));
EXPECT_EQ(requestedSampleRate1, sampleRate);
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
EXPECT_EQ(requestedSampleRate2, sampleRate);
// Delete the builder.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder1));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder1));
// Now it should no longer be valid.
// Note that test assumes we are using the HandleTracker. If we use plain pointers
// then it will be difficult to detect this kind of error.
- result = OboeStreamBuilder_getSampleRate(oboeBuilder1, &sampleRate); // stale token
- EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, result);
+ result = AAudioStreamBuilder_getSampleRate(aaudioBuilder1, &sampleRate); // stale token
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, result);
// Second builder should still be valid.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_getSampleRate(oboeBuilder2, &sampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_getSampleRate(aaudioBuilder2, &sampleRate));
EXPECT_EQ(requestedSampleRate2, sampleRate);
// Delete the second builder.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder2));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder2));
// Now it should no longer be valid. Assumes HandlerTracker used.
- EXPECT_EQ(OBOE_ERROR_INVALID_HANDLE, OboeStreamBuilder_getSampleRate(oboeBuilder2,
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_HANDLE, AAudioStreamBuilder_getSampleRate(aaudioBuilder2,
&sampleRate));
}
// Test creating a default stream with everything unspecified.
-TEST(test_aaudio, oboe_stream_unspecified) {
- OboeStreamBuilder oboeBuilder;
- OboeStream oboeStream;
- oboe_result_t result = OBOE_OK;
+TEST(test_aaudio, aaudio_stream_unspecified) {
+ AAudioStreamBuilder aaudioBuilder;
+ AAudioStream aaudioStream;
+ aaudio_result_t result = AAUDIO_OK;
- // Use an OboeStreamBuilder to define the stream.
- result = Oboe_createStreamBuilder(&oboeBuilder);
- ASSERT_EQ(OBOE_OK, result);
+ // Use an AAudioStreamBuilder to define the stream.
+ result = AAudio_createStreamBuilder(&aaudioBuilder);
+ ASSERT_EQ(AAUDIO_OK, result);
- // Create an OboeStream using the Builder.
- ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
// Cleanup
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
- EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
// Can only close once. Second time should cause an error.
- EXPECT_NE(OBOE_OK, OboeStream_close(oboeStream));
+ EXPECT_NE(AAUDIO_OK, AAudioStream_close(aaudioStream));
}
-// Test Writing to an OboeStream
-void runtest_oboe_stream(oboe_sharing_mode_t requestedSharingMode) {
- const oboe_sample_rate_t requestedSampleRate = 48000;
- const oboe_sample_rate_t requestedSamplesPerFrame = 2;
- const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+// Test Writing to an AAudioStream
+void runtest_aaudio_stream(aaudio_sharing_mode_t requestedSharingMode) {
+ const aaudio_sample_rate_t requestedSampleRate = 48000;
+ const aaudio_sample_rate_t requestedSamplesPerFrame = 2;
+ const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM16;
- oboe_sample_rate_t actualSampleRate = -1;
+ aaudio_sample_rate_t actualSampleRate = -1;
int32_t actualSamplesPerFrame = -1;
- oboe_audio_format_t actualDataFormat = OBOE_AUDIO_FORMAT_INVALID;
- oboe_sharing_mode_t actualSharingMode;
- oboe_size_frames_t framesPerBurst = -1;
+ aaudio_audio_format_t actualDataFormat = AAUDIO_FORMAT_INVALID;
+ aaudio_sharing_mode_t actualSharingMode;
+ aaudio_size_frames_t framesPerBurst = -1;
int writeLoops = 0;
- oboe_size_frames_t framesWritten = 0;
- oboe_position_frames_t framesTotal = 0;
- oboe_position_frames_t oboeFramesRead = 0;
- oboe_position_frames_t oboeFramesRead1 = 0;
- oboe_position_frames_t oboeFramesRead2 = 0;
- oboe_position_frames_t oboeFramesWritten = 0;
+ aaudio_size_frames_t framesWritten = 0;
+ aaudio_position_frames_t framesTotal = 0;
+ aaudio_position_frames_t aaudioFramesRead = 0;
+ aaudio_position_frames_t aaudioFramesRead1 = 0;
+ aaudio_position_frames_t aaudioFramesRead2 = 0;
+ aaudio_position_frames_t aaudioFramesWritten = 0;
- oboe_nanoseconds_t timeoutNanos;
+ aaudio_nanoseconds_t timeoutNanos;
- oboe_stream_state_t state = OBOE_STREAM_STATE_UNINITIALIZED;
- OboeStreamBuilder oboeBuilder;
- OboeStream oboeStream;
+ aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ AAudioStreamBuilder aaudioBuilder;
+ AAudioStream aaudioStream;
- oboe_result_t result = OBOE_OK;
+ aaudio_result_t result = AAUDIO_OK;
- // Use an OboeStreamBuilder to define the stream.
- result = Oboe_createStreamBuilder(&oboeBuilder);
- ASSERT_EQ(OBOE_OK, result);
+ // Use an AAudioStreamBuilder to define the stream.
+ result = AAudio_createStreamBuilder(&aaudioBuilder);
+ ASSERT_EQ(AAUDIO_OK, result);
// Request stream properties.
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSampleRate(oboeBuilder, requestedSampleRate));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSamplesPerFrame(oboeBuilder, requestedSamplesPerFrame));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setFormat(oboeBuilder, requestedDataFormat));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_setSharingMode(oboeBuilder, requestedSharingMode));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSampleRate(aaudioBuilder, requestedSampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSamplesPerFrame(aaudioBuilder, requestedSamplesPerFrame));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode));
- // Create an OboeStream using the Builder.
- ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
- EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
- EXPECT_EQ(OBOE_STREAM_STATE_OPEN, state);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
+ EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
// Check to see what kind of stream we actually got.
- EXPECT_EQ(OBOE_OK, OboeStream_getSampleRate(oboeStream, &actualSampleRate));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getSampleRate(aaudioStream, &actualSampleRate));
ASSERT_TRUE(actualSampleRate >= 44100 && actualSampleRate <= 96000); // TODO what is range?
- EXPECT_EQ(OBOE_OK, OboeStream_getSamplesPerFrame(oboeStream, &actualSamplesPerFrame));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getSamplesPerFrame(aaudioStream, &actualSamplesPerFrame));
ASSERT_TRUE(actualSamplesPerFrame >= 1 && actualSamplesPerFrame <= 16); // TODO what is max?
- EXPECT_EQ(OBOE_OK, OboeStream_getSharingMode(oboeStream, &actualSharingMode));
- ASSERT_TRUE(actualSharingMode == OBOE_SHARING_MODE_EXCLUSIVE
- || actualSharingMode == OBOE_SHARING_MODE_LEGACY);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getSharingMode(aaudioStream, &actualSharingMode));
+ ASSERT_TRUE(actualSharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
+ || actualSharingMode == AAUDIO_SHARING_MODE_LEGACY);
- EXPECT_EQ(OBOE_OK, OboeStream_getFormat(oboeStream, &actualDataFormat));
- EXPECT_NE(OBOE_AUDIO_FORMAT_INVALID, actualDataFormat);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFormat(aaudioStream, &actualDataFormat));
+ EXPECT_NE(AAUDIO_FORMAT_INVALID, actualDataFormat);
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesPerBurst(oboeStream, &framesPerBurst));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesPerBurst(aaudioStream, &framesPerBurst));
ASSERT_TRUE(framesPerBurst >= 16 && framesPerBurst <= 1024); // TODO what is min/max?
// Allocate a buffer for the audio data.
// TODO handle possibility of other data formats
- ASSERT_TRUE(actualDataFormat == OBOE_AUDIO_FORMAT_PCM16);
+ ASSERT_TRUE(actualDataFormat == AAUDIO_FORMAT_PCM16);
size_t dataSizeSamples = framesPerBurst * actualSamplesPerFrame;
int16_t *data = (int16_t *) calloc(dataSizeSamples, sizeof(int16_t));
ASSERT_TRUE(nullptr != data);
@@ -188,7 +188,7 @@
// Prime the buffer.
timeoutNanos = 0;
do {
- framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
// There should be some room for priming the buffer.
framesTotal += framesWritten;
ASSERT_GE(framesWritten, 0);
@@ -200,170 +200,170 @@
// Write some data and measure the rate to see if the timing is OK.
for (int numLoops = 0; numLoops < 2; numLoops++) {
// Start and wait for server to respond.
- ASSERT_EQ(OBOE_OK, OboeStream_requestStart(oboeStream));
- ASSERT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
- OBOE_STREAM_STATE_STARTING,
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_STARTING,
&state,
DEFAULT_STATE_TIMEOUT));
- EXPECT_EQ(OBOE_STREAM_STATE_STARTED, state);
+ EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
// Write some data while we are running. Read counter should be advancing.
writeLoops = 1 * actualSampleRate / framesPerBurst; // 1 second
ASSERT_LT(2, writeLoops); // detect absurdly high framesPerBurst
- timeoutNanos = 10 * OBOE_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
+ timeoutNanos = 10 * AAUDIO_NANOS_PER_SECOND * framesPerBurst / actualSampleRate; // bursts
framesWritten = 1;
- ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
- oboeFramesRead1 = oboeFramesRead;
- oboe_nanoseconds_t beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+ aaudioFramesRead1 = aaudioFramesRead;
+ aaudio_nanoseconds_t beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
do {
- framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
ASSERT_GE(framesWritten, 0);
ASSERT_LE(framesWritten, framesPerBurst);
framesTotal += framesWritten;
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
- EXPECT_EQ(framesTotal, oboeFramesWritten);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
+ EXPECT_EQ(framesTotal, aaudioFramesWritten);
// Try to get a more accurate measure of the sample rate.
if (beginTime == 0) {
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
- if (oboeFramesRead > oboeFramesRead1) { // is read pointer advancing
- beginTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
- oboeFramesRead1 = oboeFramesRead;
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+ if (aaudioFramesRead > aaudioFramesRead1) { // is read pointer advancing
+ beginTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
+ aaudioFramesRead1 = aaudioFramesRead;
}
}
} while (framesWritten > 0 && writeLoops-- > 0);
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
- oboe_nanoseconds_t endTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
- ASSERT_GT(oboeFramesRead2, 0);
- ASSERT_GT(oboeFramesRead2, oboeFramesRead1);
- ASSERT_LE(oboeFramesRead2, oboeFramesWritten);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
+ aaudio_nanoseconds_t endTime = AAudio_getNanoseconds(AAUDIO_CLOCK_MONOTONIC);
+ ASSERT_GT(aaudioFramesRead2, 0);
+ ASSERT_GT(aaudioFramesRead2, aaudioFramesRead1);
+ ASSERT_LE(aaudioFramesRead2, aaudioFramesWritten);
// TODO why is legacy so inaccurate?
const double rateTolerance = 200.0; // arbitrary tolerance for sample rate
- if (requestedSharingMode != OBOE_SHARING_MODE_LEGACY) {
+ if (requestedSharingMode != AAUDIO_SHARING_MODE_LEGACY) {
// Calculate approximate sample rate and compare with stream rate.
- double seconds = (endTime - beginTime) / (double) OBOE_NANOS_PER_SECOND;
- double measuredRate = (oboeFramesRead2 - oboeFramesRead1) / seconds;
+ double seconds = (endTime - beginTime) / (double) AAUDIO_NANOS_PER_SECOND;
+ double measuredRate = (aaudioFramesRead2 - aaudioFramesRead1) / seconds;
ASSERT_NEAR(actualSampleRate, measuredRate, rateTolerance);
}
// Request async pause and wait for server to say that it has completed the pause.
- ASSERT_EQ(OBOE_OK, OboeStream_requestPause(oboeStream));
- EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
- OBOE_STREAM_STATE_PAUSING,
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_PAUSING,
&state,
DEFAULT_STATE_TIMEOUT));
- EXPECT_EQ(OBOE_STREAM_STATE_PAUSED, state);
+ EXPECT_EQ(AAUDIO_STREAM_STATE_PAUSED, state);
}
// Make sure the read counter is not advancing when we are paused.
- ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
- ASSERT_GE(oboeFramesRead, oboeFramesRead2); // monotonic increase
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+ ASSERT_GE(aaudioFramesRead, aaudioFramesRead2); // monotonic increase
// Use this to sleep by waiting for something that won't happen.
- OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state, timeoutNanos);
- ASSERT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead2));
- EXPECT_EQ(oboeFramesRead, oboeFramesRead2);
+ AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state, timeoutNanos);
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead2));
+ EXPECT_EQ(aaudioFramesRead, aaudioFramesRead2);
// ------------------- TEST FLUSH -----------------
// Prime the buffer.
timeoutNanos = 0;
writeLoops = 100;
do {
- framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
framesTotal += framesWritten;
} while (framesWritten > 0 && writeLoops-- > 0);
EXPECT_EQ(0, framesWritten);
// Flush and wait for server to respond.
- ASSERT_EQ(OBOE_OK, OboeStream_requestFlush(oboeStream));
- EXPECT_EQ(OBOE_OK, OboeStream_waitForStateChange(oboeStream,
- OBOE_STREAM_STATE_FLUSHING,
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestFlush(aaudioStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_FLUSHING,
&state,
DEFAULT_STATE_TIMEOUT));
- EXPECT_EQ(OBOE_STREAM_STATE_FLUSHED, state);
+ EXPECT_EQ(AAUDIO_STREAM_STATE_FLUSHED, state);
// After a flush, the read counter should be caught up with the write counter.
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesWritten(oboeStream, &oboeFramesWritten));
- EXPECT_EQ(framesTotal, oboeFramesWritten);
- EXPECT_EQ(OBOE_OK, OboeStream_getFramesRead(oboeStream, &oboeFramesRead));
- EXPECT_EQ(oboeFramesRead, oboeFramesWritten);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesWritten(aaudioStream, &aaudioFramesWritten));
+ EXPECT_EQ(framesTotal, aaudioFramesWritten);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getFramesRead(aaudioStream, &aaudioFramesRead));
+ EXPECT_EQ(aaudioFramesRead, aaudioFramesWritten);
// The buffer should be empty after a flush so we should be able to write.
- framesWritten = OboeStream_write(oboeStream, data, framesPerBurst, timeoutNanos);
+ framesWritten = AAudioStream_write(aaudioStream, data, framesPerBurst, timeoutNanos);
// There should be some room for priming the buffer.
ASSERT_TRUE(framesWritten > 0 && framesWritten <= framesPerBurst);
- EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
free(data);
}
-// Test Writing to an OboeStream using LEGACY sharing mode.
-TEST(test_aaudio, oboe_stream_legacy) {
- runtest_oboe_stream(OBOE_SHARING_MODE_LEGACY);
+// Test Writing to an AAudioStream using LEGACY sharing mode.
+TEST(test_aaudio, aaudio_stream_legacy) {
+ runtest_aaudio_stream(AAUDIO_SHARING_MODE_LEGACY);
}
/* TODO Enable exclusive mode test.
-// Test Writing to an OboeStream using EXCLUSIVE sharing mode.
-TEST(test_aaudio, oboe_stream_exclusive) {
- runtest_oboe_stream(OBOE_SHARING_MODE_EXCLUSIVE);
+// Test Writing to an AAudioStream using EXCLUSIVE sharing mode.
+TEST(test_aaudio, aaudio_stream_exclusive) {
+ runtest_aaudio_stream(AAUDIO_SHARING_MODE_EXCLUSIVE);
}
*/
-#define OBOE_THREAD_ANSWER 1826375
-#define OBOE_THREAD_DURATION_MSEC 500
+#define AAUDIO_THREAD_ANSWER 1826375
+#define AAUDIO_THREAD_DURATION_MSEC 500
-static void *TestOboeStreamThreadProc(void *arg) {
- OboeStream oboeStream = (OboeStream) reinterpret_cast<size_t>(arg);
- oboe_stream_state_t state;
+static void *TestAAudioStreamThreadProc(void *arg) {
+ AAudioStream aaudioStream = (AAudioStream) reinterpret_cast<size_t>(arg);
+ aaudio_stream_state_t state;
// Use this to sleep by waiting for something that won't happen.
- EXPECT_EQ(OBOE_OK, OboeStream_getState(oboeStream, &state));
- OboeStream_waitForStateChange(oboeStream, OBOE_STREAM_STATE_PAUSED, &state,
- OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND);
- return reinterpret_cast<void *>(OBOE_THREAD_ANSWER);
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_getState(aaudioStream, &state));
+ AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSED, &state,
+ AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND);
+ return reinterpret_cast<void *>(AAUDIO_THREAD_ANSWER);
}
// Test creating a stream related thread.
-TEST(test_aaudio, oboe_stream_thread_basic) {
- OboeStreamBuilder oboeBuilder;
- OboeStream oboeStream;
- oboe_result_t result = OBOE_OK;
+TEST(test_aaudio, aaudio_stream_thread_basic) {
+ AAudioStreamBuilder aaudioBuilder;
+ AAudioStream aaudioStream;
+ aaudio_result_t result = AAUDIO_OK;
void *threadResult;
- // Use an OboeStreamBuilder to define the stream.
- result = Oboe_createStreamBuilder(&oboeBuilder);
- ASSERT_EQ(OBOE_OK, result);
+ // Use an AAudioStreamBuilder to define the stream.
+ result = AAudio_createStreamBuilder(&aaudioBuilder);
+ ASSERT_EQ(AAUDIO_OK, result);
- // Create an OboeStream using the Builder.
- ASSERT_EQ(OBOE_OK, OboeStreamBuilder_openStream(oboeBuilder, &oboeStream));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
// Start a thread.
- ASSERT_EQ(OBOE_OK, OboeStream_createThread(oboeStream,
- 10 * OBOE_NANOS_PER_MILLISECOND,
- TestOboeStreamThreadProc,
- reinterpret_cast<void *>(oboeStream)));
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_createThread(aaudioStream,
+ 10 * AAUDIO_NANOS_PER_MILLISECOND,
+ TestAAudioStreamThreadProc,
+ reinterpret_cast<void *>(aaudioStream)));
// Thread already started.
- ASSERT_NE(OBOE_OK, OboeStream_createThread(oboeStream, // should fail!
- 10 * OBOE_NANOS_PER_MILLISECOND,
- TestOboeStreamThreadProc,
- reinterpret_cast<void *>(oboeStream)));
+ ASSERT_NE(AAUDIO_OK, AAudioStream_createThread(aaudioStream, // should fail!
+ 10 * AAUDIO_NANOS_PER_MILLISECOND,
+ TestAAudioStreamThreadProc,
+ reinterpret_cast<void *>(aaudioStream)));
// Wait for the thread to finish.
- ASSERT_EQ(OBOE_OK, OboeStream_joinThread(oboeStream,
- &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_joinThread(aaudioStream,
+ &threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
// The thread returns a special answer.
- ASSERT_EQ(OBOE_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
+ ASSERT_EQ(AAUDIO_THREAD_ANSWER, (int)reinterpret_cast<size_t>(threadResult));
// Thread should already be joined.
- ASSERT_NE(OBOE_OK, OboeStream_joinThread(oboeStream, // should fail!
- &threadResult, 2 * OBOE_THREAD_DURATION_MSEC * OBOE_NANOS_PER_MILLISECOND));
+ ASSERT_NE(AAUDIO_OK, AAudioStream_joinThread(aaudioStream, // should fail!
+ &threadResult, 2 * AAUDIO_THREAD_DURATION_MSEC * AAUDIO_NANOS_PER_MILLISECOND));
// Cleanup
- EXPECT_EQ(OBOE_OK, OboeStreamBuilder_delete(oboeBuilder));
- EXPECT_EQ(OBOE_OK, OboeStream_close(oboeStream));
+ EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
}
int main(int argc, char **argv) {
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index b8478d2..185ebfa 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -387,6 +387,10 @@
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
ConnectivityReceiver.prepare();
toggleWifi();
@@ -400,6 +404,10 @@
}
public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
ConnectivityReceiver.prepare();
ConnectivityReceiver receiver = new ConnectivityReceiver();
IntentFilter filter = new IntentFilter();
@@ -416,6 +424,10 @@
public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
throws InterruptedException {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
Intent startIntent = new Intent();
startIntent.setComponent(new ComponentName("android.net.cts.appForApi23",
"android.net.cts.appForApi23.ConnectivityListeningActivity"));
diff --git a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
index 68609e1..4531aa68 100644
--- a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
@@ -32,7 +32,7 @@
private static final String SECURITY_PATCH_DATE_ERROR =
"ro.build.version.security_patch should be \"%d-%02d\" or later. Found \"%s\"";
private static final int SECURITY_PATCH_YEAR = 2016;
- private static final int SECURITY_PATCH_MONTH = 06;
+ private static final int SECURITY_PATCH_MONTH = 12;
private boolean mSkipTests = false;
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index df82225..845cf30 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -785,6 +785,92 @@
assertTrue("/data is not mounted NODEV", (vfs.f_flag & OsConstants.ST_NODEV) != 0);
}
+ public void testAllBlockDevicesAreSecure() throws Exception {
+ Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFBLK);
+ assertTrue("Found insecure block devices: " + insecure.toString(),
+ insecure.isEmpty());
+ }
+
+ private static final Set<File> CHAR_DEV_EXCEPTIONS = new HashSet<File>(
+ Arrays.asList(
+ // All exceptions should be alphabetical and associated with a bug number.
+ new File("/dev/adsprpc-smd"), // b/11710243
+ new File("/dev/alarm"), // b/9035217
+ new File("/dev/ashmem"),
+ new File("/dev/binder"),
+ new File("/dev/card0"), // b/13159510
+ new File("/dev/renderD128"),
+ new File("/dev/renderD129"), // b/23798677
+ new File("/dev/dri/card0"), // b/13159510
+ new File("/dev/dri/renderD128"),
+ new File("/dev/dri/renderD129"), // b/23798677
+ new File("/dev/felica"), // b/11142586
+ new File("/dev/felica_ant"), // b/11142586
+ new File("/dev/felica_cen"), // b/11142586
+ new File("/dev/felica_pon"), // b/11142586
+ new File("/dev/felica_rfs"), // b/11142586
+ new File("/dev/felica_rws"), // b/11142586
+ new File("/dev/felica_uicc"), // b/11142586
+ new File("/dev/full"),
+ new File("/dev/galcore"),
+ new File("/dev/genlock"), // b/9035217
+ new File("/dev/graphics/galcore"),
+ new File("/dev/ion"),
+ new File("/dev/kgsl-2d0"), // b/11271533
+ new File("/dev/kgsl-2d1"), // b/11271533
+ new File("/dev/kgsl-3d0"), // b/9035217
+ new File("/dev/log/events"), // b/9035217
+ new File("/dev/log/main"), // b/9035217
+ new File("/dev/log/radio"), // b/9035217
+ new File("/dev/log/system"), // b/9035217
+ new File("/dev/mali0"), // b/9106968
+ new File("/dev/mali"), // b/11142586
+ new File("/dev/mm_interlock"), // b/12955573
+ new File("/dev/mm_isp"), // b/12955573
+ new File("/dev/mm_v3d"), // b/12955573
+ new File("/dev/msm_rotator"), // b/9035217
+ new File("/dev/null"),
+ new File("/dev/nvhost-as-gpu"),
+ new File("/dev/nvhost-ctrl"), // b/9088251
+ new File("/dev/nvhost-ctrl-gpu"),
+ new File("/dev/nvhost-dbg-gpu"),
+ new File("/dev/nvhost-gpu"),
+ new File("/dev/nvhost-gr2d"), // b/9088251
+ new File("/dev/nvhost-gr3d"), // b/9088251
+ new File("/dev/nvhost-tsec"),
+ new File("/dev/nvhost-prof-gpu"),
+ new File("/dev/nvhost-vic"),
+ new File("/dev/nvmap"), // b/9088251
+ new File("/dev/pmsg0"), // b/31857082
+ new File("/dev/ptmx"), // b/9088251
+ new File("/dev/pvrsrvkm"), // b/9108170
+ new File("/dev/pvr_sync"),
+ new File("/dev/quadd"),
+ new File("/dev/random"),
+ new File("/dev/snfc_cen"), // b/11142586
+ new File("/dev/snfc_hsel"), // b/11142586
+ new File("/dev/snfc_intu_poll"), // b/11142586
+ new File("/dev/snfc_rfs"), // b/11142586
+ new File("/dev/tegra-throughput"),
+ new File("/dev/tiler"), // b/9108170
+ new File("/dev/tty"),
+ new File("/dev/urandom"),
+ new File("/dev/ump"), // b/11142586
+ new File("/dev/xt_qtaguid"), // b/9088251
+ new File("/dev/zero"),
+ new File("/dev/fimg2d"), // b/10428016
+ new File("/dev/mobicore-user") // b/10428016
+ ));
+
+ public void testAllCharacterDevicesAreSecure() throws Exception {
+ Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFCHR);
+ Set<File> insecurePts = getAllInsecureDevicesInDirAndSubdir(new File("/dev/pts"), FileUtils.S_IFCHR);
+ insecure.removeAll(CHAR_DEV_EXCEPTIONS);
+ insecure.removeAll(insecurePts);
+ assertTrue("Found insecure character devices: " + insecure.toString(),
+ insecure.isEmpty());
+ }
+
public void testDevRandomWorldReadableAndWritable() throws Exception {
File f = new File("/dev/random");
@@ -898,6 +984,67 @@
.fileHasOnly("/system/bin/run-as"));
}
+ private static Set<File>
+ getAllInsecureDevicesInDirAndSubdir(File dir, int type) throws Exception {
+ assertTrue(dir.isDirectory());
+ Set<File> retval = new HashSet<File>();
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return retval;
+ }
+
+ File[] subDirectories = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+
+
+ /* recurse into subdirectories */
+ if (subDirectories != null) {
+ for (File f : subDirectories) {
+ retval.addAll(getAllInsecureDevicesInDirAndSubdir(f, type));
+ }
+ }
+
+ File[] filesInThisDirectory = dir.listFiles();
+ if (filesInThisDirectory == null) {
+ return retval;
+ }
+
+ for (File f: filesInThisDirectory) {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ FileUtils.getFileStatus(f.getAbsolutePath(), status, false);
+ if (status.isOfType(type)) {
+ if (f.canRead() || f.canWrite() || f.canExecute()) {
+ retval.add(f);
+ }
+ if (status.uid == 2000) {
+ // The shell user should not own any devices
+ retval.add(f);
+ }
+
+ // Don't allow devices owned by GIDs
+ // accessible to non-privileged applications.
+ if ((status.gid == 1007) // AID_LOG
+ || (status.gid == 1015) // AID_SDCARD_RW
+ || (status.gid == 1023) // AID_MEDIA_RW
+ || (status.gid == 1028) // AID_SDCARD_R
+ || (status.gid == 2000)) // AID_SHELL
+ {
+ if (status.hasModeFlag(FileUtils.S_IRGRP)
+ || status.hasModeFlag(FileUtils.S_IWGRP)
+ || status.hasModeFlag(FileUtils.S_IXGRP))
+ {
+ retval.add(f);
+ }
+ }
+ }
+ }
+ return retval;
+ }
+
private Set<File> getWritableDirectoriesAndSubdirectoriesOf(File dir) throws Exception {
Set<File> retval = new HashSet<File>();
if (!dir.isDirectory()) {
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index b471286..9c997f2 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1757,14 +1757,6 @@
<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
- <!-- Allows applications to set the system time zone.
- <p>Protection level: normal
- -->
- <permission android:name="android.permission.SET_TIME_ZONE"
- android:label="@string/permlab_setTimeZone"
- android:description="@string/permdesc_setTimeZone"
- android:protectionLevel="normal" />
-
<!-- ==================================================== -->
<!-- Permissions related to changing status bar -->
<!-- ==================================================== -->
@@ -3363,7 +3355,7 @@
</service>
<service
- android:name="com.android.server.pm.BackgroundDexOptService"
+ android:name="com.android.server.BackgroundDexOptJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/tests/tests/security/res/raw/bug_33897722.gif b/tests/tests/security/res/raw/bug_33897722.gif
new file mode 100755
index 0000000..7a563d7
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_33897722.gif
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index c1f59ae..fc2a342 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -36,7 +36,6 @@
"69:69:56:2E:40:80:F4:24:A1:E7:19:9F:14:BA:F3:EE:58:AB:6A:BB",
"92:5A:8F:8D:2C:6D:04:E0:66:5F:59:6A:FF:22:D8:63:E8:25:6F:3F",
"75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
- "40:9D:4B:D9:17:B5:5C:27:B6:9B:64:CB:98:22:44:0D:CD:09:B8:89",
"E1:9F:E3:0E:8B:84:60:9E:80:9B:17:0D:72:A8:C5:BA:6E:14:09:BD",
"DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13",
"4F:99:AA:93:FB:2B:D1:37:26:A1:99:4A:CE:7F:F0:05:F2:93:5D:1E",
@@ -82,7 +81,6 @@
"1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
"6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
"A9:E9:78:08:14:37:58:88:F2:05:19:B0:6D:2B:0D:2B:60:16:90:7D",
- "60:D6:89:74:B5:C2:65:9E:8A:0F:C1:88:7C:88:D2:46:69:1B:18:2C",
"D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
"66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
"DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
@@ -96,16 +94,15 @@
"05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
"62:52:DC:40:F7:11:43:A2:2F:DE:9E:F7:34:8E:06:42:51:B1:81:18",
"70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
- "A0:A1:AB:90:C9:FC:84:7B:3B:12:61:E8:97:7D:5F:D3:22:61:D3:CC",
"D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
"B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
"2E:14:DA:EC:28:F0:FA:1E:8E:38:9A:4E:AB:EB:26:C0:0A:D3:83:C3",
"DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
+ "0D:44:DD:8C:3C:8C:1A:1A:58:75:64:81:E9:0F:2E:2A:FF:B3:D2:6E",
"CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
"13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
"5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
"49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
- "25:01:90:19:CF:FB:D9:99:1C:B7:68:25:74:8D:94:5F:30:93:95:42",
"B5:1C:06:7C:EE:2B:0C:3D:F8:55:AB:2D:92:F4:FE:39:D4:E7:0F:0E",
"29:36:21:02:8B:20:ED:02:F5:66:C5:32:D1:D6:ED:90:9F:45:00:2F",
"37:9A:19:7B:41:85:45:35:0C:A6:03:69:F3:3C:2E:AF:47:4F:20:79",
@@ -140,12 +137,14 @@
"20:D8:06:40:DF:9B:25:F5:12:25:3A:11:EA:F7:59:8A:EB:14:B5:47",
"CF:9E:87:6D:D3:EB:FC:42:26:97:A3:B5:A3:7A:A0:76:A9:06:23:48",
"2B:B1:F5:3E:55:0C:1D:C5:F1:D4:E6:B7:6A:46:4B:55:06:02:AC:21",
+ "EC:50:35:07:B2:15:C4:95:62:19:E2:A8:9A:5B:42:99:2C:4C:2C:20",
"47:BE:AB:C9:22:EA:E8:0E:78:78:34:62:A7:9F:45:C2:54:FD:E6:8B",
"3A:44:73:5A:E5:81:90:1F:24:86:61:46:1E:3B:9C:C4:5F:F5:3A:1B",
"B3:1E:B1:B7:40:E3:6C:84:02:DA:DC:37:D4:4D:F5:D4:67:49:52:F9",
"F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
"3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
"03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
+ "1E:0E:56:19:0A:D1:8B:25:98:B2:04:44:FF:66:8A:04:17:99:5F:3F",
"DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4",
"51:C6:E7:08:49:06:6E:F3:92:D4:5C:A0:0D:6D:A3:62:8F:C3:52:39",
"D3:DD:48:3E:2B:BF:4C:05:E8:AF:10:F5:FA:76:26:CF:D3:DC:30:92",
@@ -154,6 +153,7 @@
"59:0D:2D:7D:88:4F:40:2E:61:7E:A5:62:32:17:65:CF:17:D8:94:E9",
"AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
"DF:71:7E:AA:4A:D9:4E:C9:55:84:99:60:2D:48:DE:5F:BC:F0:3A:25",
+ "F6:10:84:07:D6:F8:BB:67:98:0C:C2:E2:44:C2:EB:AE:1C:EF:63:BE",
"AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4",
"D2:7A:D2:BE:ED:94:C0:A1:3C:C7:25:21:EA:5D:71:BE:81:19:F3:2B",
"5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
@@ -175,10 +175,10 @@
"6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
"31:F1:FD:68:22:63:20:EE:C6:3B:3F:9D:EA:4A:3E:53:7C:7C:39:17",
"23:88:C9:D3:71:CC:9E:96:3D:FF:7D:3C:A7:CE:FC:D6:25:EC:19:0D",
- "8C:96:BA:EB:DD:2B:07:07:48:EE:30:32:66:A0:F3:98:6E:7C:AE:58",
"7F:8A:B0:CF:D0:51:87:6A:66:F3:36:0F:47:C8:8D:8C:D3:35:FC:74",
"4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
- "A0:73:E5:C5:BD:43:61:0D:86:4C:21:13:0A:85:58:57:CC:9C:EA:46",
+ "5A:8C:EF:45:D7:A6:98:59:76:7A:8C:8B:44:96:B5:78:CF:47:4B:1A",
+ "8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16",
"B1:2E:13:63:45:86:A4:6F:1A:B2:60:68:37:58:2D:C4:AC:FD:94:97",
"04:83:ED:33:99:AC:36:08:05:87:22:ED:BC:5E:46:00:E3:BE:F9:D7",
};
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index d849126..1a49d7f 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -16,12 +16,13 @@
package android.security.cts;
+import com.android.compatibility.common.util.PropertyUtil;
+
import android.test.AndroidTestCase;
import junit.framework.TestCase;
import android.app.ActivityManager;
import android.content.Context;
-import android.os.SystemProperties;
import android.util.Log;
import java.io.BufferedReader;
import java.io.FileReader;
@@ -34,7 +35,7 @@
System.loadLibrary("ctssecurity_jni");
}
- private static final int min_api_level = 23;
+ private static final int MIN_API_LEVEL = 23;
private static final String TAG = "EncryptionTest";
@@ -50,15 +51,8 @@
}
private boolean isRequired() {
- int first_api_level =
- SystemProperties.getInt("ro.product.first_api_level", 0);
-
- // Optional before min_api_level or if the device has low RAM
- if (first_api_level > 0 && first_api_level < min_api_level) {
- return false;
- } else {
- return !hasLowRAM();
- }
+ // Optional before MIN_API_LEVEL or if the device has low RAM
+ return PropertyUtil.getFirstApiLevel() >= MIN_API_LEVEL && !hasLowRAM();
}
public void testEncryption() throws Exception {
diff --git a/tests/tests/security/src/android/security/cts/Movie33897722.java b/tests/tests/security/src/android/security/cts/Movie33897722.java
new file mode 100644
index 0000000..f6859da
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/Movie33897722.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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 android.security.cts;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Movie;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+
+import android.security.cts.R;
+
+public class Movie33897722 extends AndroidTestCase {
+ /**
+ * Verifies that decoding a particular GIF file does not read out out of bounds.
+ *
+ * The image has a color map of size 2, but states that pixels should come from values
+ * larger than 2. Ensure that we do not attempt to read colors from beyond the end of the
+ * color map, which would be reading memory that we do not control, and may be uninitialized.
+ */
+ public void test_android_bug_33897722() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33897722);
+ Movie movie = Movie.decodeStream(exploitImage);
+ assertNotNull(movie);
+ assertEquals(movie.width(), 600);
+ assertEquals(movie.height(), 752);
+
+ // The image has a 10 x 10 frame on top of a transparent background. Only test the
+ // 10 x 10 frame, since the original bug would never have used uninitialized memory
+ // outside of it.
+ Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+
+ // Use Src PorterDuff mode, to see exactly what the Movie creates.
+ Paint paint = new Paint();
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+ movie.draw(canvas, 0, 0, paint);
+
+ for (int x = 0; x < 10; x++) {
+ for (int y = 0; y < 10; y++) {
+ assertEquals(bitmap.getPixel(x, y), Color.TRANSPARENT);
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index 5fa698e..eb162fb 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -16,6 +16,8 @@
package android.security.cts;
+import android.platform.test.annotations.SecurityTest;
+
import junit.framework.TestCase;
public class NativeCodeTest extends TestCase {
@@ -24,6 +26,7 @@
System.loadLibrary("ctssecurity_jni");
}
+ @SecurityTest
public void testVroot() throws Exception {
assertTrue("Device is vulnerable to CVE-2013-6282. Please apply security patch at "
+ "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
@@ -31,6 +34,7 @@
+ "8404663f81d212918ff85f493649a7991209fa04", doVrootTest());
}
+ @SecurityTest
public void testPerfEvent() throws Exception {
assertFalse("Device is vulnerable to CVE-2013-2094. Please apply security patch "
+ "at http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/"
@@ -38,10 +42,12 @@
doPerfEventTest());
}
+ @SecurityTest
public void testPerfEvent2() throws Exception {
assertTrue(doPerfEventTest2());
}
+ @SecurityTest
public void testFutex() throws Exception {
assertTrue("Device is vulnerable to CVE-2014-3153, a vulnerability in the futex() system "
+ "call. Please apply the security patch at "
@@ -50,6 +56,7 @@
doFutexTest());
}
+ @SecurityTest
public void testNvmapIocFromId() throws Exception {
assertTrue("Device is vulnerable to CVE-2014-5332. "
+ "NVIDIA has released code fixes to upstream repositories and device vendors. "
@@ -58,6 +65,7 @@
doNvmapIocFromIdTest());
}
+ @SecurityTest
public void testPingPongRoot() throws Exception {
assertTrue("Device is vulnerable to CVE-2015-3636, a vulnerability in the ping "
+ "socket implementation. Please apply the security patch at "
@@ -65,6 +73,7 @@
doPingPongRootTest());
}
+ @SecurityTest
public void testPipeReadV() throws Exception {
assertTrue("Device is vulnerable to CVE-2015-1805 and/or CVE-2016-0774,"
+ " a vulnerability in the pipe_read() function."
@@ -74,6 +83,7 @@
doPipeReadVTest());
}
+ @SecurityTest
public void testSysVipc() throws Exception {
assertTrue("Android does not support Sys V IPC, it must "
+ "be removed from the kernel. In the kernel config: "
@@ -114,6 +124,7 @@
*/
private static native boolean doVrootTest();
+ @SecurityTest
public void testCVE20141710() throws Exception {
assertTrue("Device is vulnerable to CVE-2014-1710", doCVE20141710Test());
}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index cb4dd1f..fd7c27d 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -70,74 +70,92 @@
before any existing test methods
***********************************************************/
+ @SecurityTest
public void testStagefright_bug_33137046() throws Exception {
doStagefrightTest(R.raw.bug_33137046);
}
+ @SecurityTest
public void testStagefright_cve_2016_2507() throws Exception {
doStagefrightTest(R.raw.cve_2016_2507);
}
+ @SecurityTest
public void testStagefright_bug_31647370() throws Exception {
doStagefrightTest(R.raw.bug_31647370);
}
+ @SecurityTest
public void testStagefright_bug_32577290() throws Exception {
doStagefrightTest(R.raw.bug_32577290);
}
+ @SecurityTest
public void testStagefright_cve_2015_1538_1() throws Exception {
doStagefrightTest(R.raw.cve_2015_1538_1);
}
+ @SecurityTest
public void testStagefright_cve_2015_1538_2() throws Exception {
doStagefrightTest(R.raw.cve_2015_1538_2);
}
+ @SecurityTest
public void testStagefright_cve_2015_1538_3() throws Exception {
doStagefrightTest(R.raw.cve_2015_1538_3);
}
+ @SecurityTest
public void testStagefright_cve_2015_1538_4() throws Exception {
doStagefrightTest(R.raw.cve_2015_1538_4);
}
+ @SecurityTest
public void testStagefright_cve_2015_1539() throws Exception {
doStagefrightTest(R.raw.cve_2015_1539);
}
+ @SecurityTest
public void testStagefright_cve_2015_3824() throws Exception {
doStagefrightTest(R.raw.cve_2015_3824);
}
+ @SecurityTest
public void testStagefright_cve_2015_3826() throws Exception {
doStagefrightTest(R.raw.cve_2015_3826);
}
+ @SecurityTest
public void testStagefright_cve_2015_3827() throws Exception {
doStagefrightTest(R.raw.cve_2015_3827);
}
+ @SecurityTest
public void testStagefright_cve_2015_3828() throws Exception {
doStagefrightTest(R.raw.cve_2015_3828);
}
+ @SecurityTest
public void testStagefright_cve_2015_3829() throws Exception {
doStagefrightTest(R.raw.cve_2015_3829);
}
+ @SecurityTest
public void testStagefright_cve_2015_3864() throws Exception {
doStagefrightTest(R.raw.cve_2015_3864);
}
+ @SecurityTest
public void testStagefright_cve_2015_6598() throws Exception {
doStagefrightTest(R.raw.cve_2015_6598);
}
+ @SecurityTest
public void testStagefright_bug_26366256() throws Exception {
doStagefrightTest(R.raw.bug_26366256);
}
+ @SecurityTest
public void testStagefright_cve_2016_2429_b_27211885() throws Exception {
doStagefrightTest(R.raw.cve_2016_2429_b_27211885);
}
@@ -147,38 +165,47 @@
before any existing test methods
***********************************************************/
+ @SecurityTest
public void testStagefright_bug_32873375() throws Exception {
doStagefrightTest(R.raw.bug_32873375);
}
+ @SecurityTest
public void testStagefright_bug_25765591() throws Exception {
doStagefrightTest(R.raw.bug_25765591);
}
+ @SecurityTest
public void testStagefright_bug_25812590() throws Exception {
doStagefrightTest(R.raw.bug_25812590);
}
+ @SecurityTest
public void testStagefright_bug_26070014() throws Exception {
doStagefrightTest(R.raw.bug_26070014);
}
+ @SecurityTest
public void testStagefright_cve_2015_3867() throws Exception {
doStagefrightTest(R.raw.cve_2015_3867);
}
+ @SecurityTest
public void testStagefright_cve_2015_3869() throws Exception {
doStagefrightTest(R.raw.cve_2015_3869);
}
+ @SecurityTest
public void testStagefright_bug_32322258() throws Exception {
doStagefrightTest(R.raw.bug_32322258);
}
+ @SecurityTest
public void testStagefright_cve_2015_3873_b_23248776() throws Exception {
doStagefrightTest(R.raw.cve_2015_3873_b_23248776);
}
+ @SecurityTest
public void testStagefright_cve_2015_3873_b_20718524() throws Exception {
doStagefrightTest(R.raw.cve_2015_3873_b_20718524);
}
@@ -193,30 +220,37 @@
doStagefrightTest(R.raw.cve_2015_3867_b_23213430);
}
+ @SecurityTest
public void testStagefright_cve_2015_3873_b_21814993() throws Exception {
doStagefrightTest(R.raw.cve_2015_3873_b_21814993);
}
+ @SecurityTest
public void testStagefright_bug_32915871() throws Exception {
doStagefrightTest(R.raw.bug_32915871);
}
+ @SecurityTest
public void testStagefright_bug_28333006() throws Exception {
doStagefrightTest(R.raw.bug_28333006);
}
+ @SecurityTest
public void testStagefright_bug_14388161() throws Exception {
doStagefrightTestMediaPlayer(R.raw.bug_14388161);
}
+ @SecurityTest
public void testStagefright_cve_2016_3755() throws Exception {
doStagefrightTest(R.raw.cve_2016_3755);
}
+ @SecurityTest
public void testStagefright_cve_2016_3878_b_29493002() throws Exception {
doStagefrightTest(R.raw.cve_2016_3878_b_29493002);
}
+ @SecurityTest
public void testStagefright_bug_27855419_CVE_2016_2463() throws Exception {
doStagefrightTest(R.raw.bug_27855419);
}
diff --git a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
index fc98adc1..151ccf2 100644
--- a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
+++ b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
@@ -18,6 +18,7 @@
import junit.framework.TestCase;
import android.content.Context;
+import android.platform.test.annotations.SecurityTest;
import android.media.audiofx.AudioEffect;
import android.media.MediaPlayer;
import android.media.audiofx.Visualizer;
@@ -36,6 +37,7 @@
}
//Testing security bug: 30229821
+ @SecurityTest
public void testVisualizer_MalformedConstructor() throws Exception {
final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
final int VISUALIZER_CMD_MEASURE = 0x10001;
diff --git a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
index f81da6b..bbc70a9 100644
--- a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
+++ b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
@@ -18,6 +18,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.platform.test.annotations.SecurityTest;
import android.test.AndroidTestCase;
import java.io.InputStream;
@@ -31,6 +32,7 @@
* Prior to fixing bug 33300701, decoding resulted in undefined behavior (divide by zero).
* With the fix, decoding will fail, without dividing by zero.
*/
+ @SecurityTest
public void test_android_bug_33300701() {
InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33300701);
Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
index a040715..aeaf06b 100644
--- a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
@@ -16,6 +16,8 @@
package android.content.pm.cts.shortcutmanager.throttling;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +31,8 @@
* Make sure that when a fg service is running, shortcut manager calls are not throttled.
*/
public class FgService extends Service {
+ private static final String NOTIFICATION_CHANNEL_ID = "cts/shortcutmanager/FgService";
+
public static void start(Context context, String replyAction) {
final Intent i =
new Intent().setComponent(new ComponentName(context, FgService.class))
@@ -40,10 +44,15 @@
public int onStartCommand(Intent intent, int flags, int startId) {
// Start as foreground.
- Notification notification = new Notification.Builder(getApplicationContext())
- .setContentTitle("FgService")
- .setSmallIcon(android.R.drawable.ic_popup_sync)
- .build();
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Notification notification =
+ new Notification.Builder(getApplicationContext(), NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("FgService")
+ .setSmallIcon(android.R.drawable.ic_popup_sync)
+ .build();
startForeground(1, notification);
final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 73ba945..eabe292 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
@@ -41,6 +42,13 @@
</intent-filter>
</service>
+ <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService" />
+ </intent-filter>
+ </service>
+
<service android:name="android.telecom.cts.MockInCallService"
android:permission="android.permission.BIND_INCALL_SERVICE" >
<intent-filter>
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index a6e92c0..86832eb 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -15,8 +15,16 @@
*/
package android.telecom.cts;
-
-import static android.telecom.cts.TestUtils.*;
+import static android.telecom.cts.TestUtils.ACCOUNT_ID;
+import static android.telecom.cts.TestUtils.ACCOUNT_LABEL;
+import static android.telecom.cts.TestUtils.COMPONENT;
+import static android.telecom.cts.TestUtils.PACKAGE;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_ID_1;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_ID_2;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_ACCOUNT_LABEL;
+import static android.telecom.cts.TestUtils.SELF_MANAGED_COMPONENT;
+import static android.telecom.cts.TestUtils.TAG;
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -47,6 +55,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
/**
* Base class for Telecom CTS tests that require a {@link CtsConnectionService} and
@@ -59,7 +68,6 @@
public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
new PhoneAccountHandle(new ComponentName(PACKAGE, COMPONENT), ACCOUNT_ID);
-
public static final PhoneAccount TEST_PHONE_ACCOUNT = PhoneAccount.builder(
TEST_PHONE_ACCOUNT_HANDLE, ACCOUNT_LABEL)
.setAddress(Uri.parse("tel:555-TEST"))
@@ -73,6 +81,38 @@
.addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
.build();
+ public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_1 =
+ new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT),
+ SELF_MANAGED_ACCOUNT_ID_1);
+ public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_1 = PhoneAccount.builder(
+ TEST_SELF_MANAGED_HANDLE_1, SELF_MANAGED_ACCOUNT_LABEL)
+ .setAddress(Uri.parse("sip:test@test.com"))
+ .setSubscriptionAddress(Uri.parse("sip:test@test.com"))
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .setHighlightColor(Color.BLUE)
+ .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+ .build();
+
+ public static final PhoneAccountHandle TEST_SELF_MANAGED_HANDLE_2 =
+ new PhoneAccountHandle(new ComponentName(PACKAGE, SELF_MANAGED_COMPONENT),
+ SELF_MANAGED_ACCOUNT_ID_2);
+ public static final PhoneAccount TEST_SELF_MANAGED_PHONE_ACCOUNT_2 = PhoneAccount.builder(
+ TEST_SELF_MANAGED_HANDLE_2, SELF_MANAGED_ACCOUNT_LABEL)
+ .setAddress(Uri.parse("sip:test@test.com"))
+ .setSubscriptionAddress(Uri.parse("sip:test@test.com"))
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .setHighlightColor(Color.BLUE)
+ .setShortDescription(SELF_MANAGED_ACCOUNT_LABEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+ .build();
+
private static int sCounter = 5549999;
Context mContext;
@@ -101,7 +141,7 @@
mContext = getInstrumentation().getContext();
mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- mShouldTestTelecom = shouldTestTelecom(mContext);
+ mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext);
if (mShouldTestTelecom) {
mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
@@ -857,6 +897,27 @@
);
}
+ /**
+ * Checks all fields of two PhoneAccounts for equality, with the exception of the enabled state.
+ * Should only be called after assertPhoneAccountRegistered when it can be guaranteed
+ * that the PhoneAccount is registered.
+ * @param expected The expected PhoneAccount.
+ * @param actual The actual PhoneAccount.
+ */
+ void assertPhoneAccountEquals(final PhoneAccount expected,
+ final PhoneAccount actual) {
+ assertEquals(expected.getAddress(), actual.getAddress());
+ assertEquals(expected.getAccountHandle(), actual.getAccountHandle());
+ assertEquals(expected.getCapabilities(), actual.getCapabilities());
+ assertTrue(areBundlesEqual(expected.getExtras(), actual.getExtras()));
+ assertEquals(expected.getHighlightColor(), actual.getHighlightColor());
+ assertEquals(expected.getIcon(), actual.getIcon());
+ assertEquals(expected.getLabel(), actual.getLabel());
+ assertEquals(expected.getShortDescription(), actual.getShortDescription());
+ assertEquals(expected.getSubscriptionAddress(), actual.getSubscriptionAddress());
+ assertEquals(expected.getSupportedUriSchemes(), actual.getSupportedUriSchemes());
+ }
+
void assertPhoneAccountRegistered(final PhoneAccountHandle handle) {
waitUntilConditionIsTrueOrTimeout(
new Condition() {
@@ -930,6 +991,24 @@
);
}
+ void assertIsInCall(boolean isIncall) {
+ waitUntilConditionIsTrueOrTimeout(
+ new Condition() {
+ @Override
+ public Object expected() {
+ return isIncall;
+ }
+
+ @Override
+ public Object actual() {
+ return mTelecomManager.isInCall();
+ }
+ },
+ WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+ "Expected isInCall to be " + isIncall
+ );
+ }
+
/**
* Asserts that a call's properties are as expected.
*
@@ -1095,6 +1174,39 @@
}
}
}
+
+ /**
+ * Waits for a predicate to return {@code true} within the specified timeout. Uses the
+ * {@link #mLock} for this {@link InvokeCounter} to eliminate the need to perform busy-wait.
+ * @param predicate The predicate.
+ * @param timeoutMillis The timeout.
+ */
+ public void waitForPredicate(Predicate predicate, long timeoutMillis) {
+ synchronized (mLock) {
+ mInvokeArgs.clear();
+ long startTimeMillis = SystemClock.uptimeMillis();
+ long elapsedTimeMillis = 0;
+ long remainingTimeMillis = timeoutMillis;
+ Object foundValue = null;
+ boolean wasFound = false;
+ do {
+ try {
+ mLock.wait(timeoutMillis);
+ foundValue = (mInvokeArgs.get(mInvokeArgs.size()-1))[0];
+ wasFound = predicate.test(foundValue);
+ elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ } while (!wasFound && remainingTimeMillis > 0);
+ if (wasFound) {
+ return;
+ } else if (remainingTimeMillis <= 0) {
+ fail("Expected value not found within time limit");
+ }
+ }
+ }
}
public static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
new file mode 100644
index 0000000..ee2f842
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 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 android.telecom.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * CTS test self-managed {@link ConnectionService} implementation.
+ */
+public class CtsSelfManagedConnectionService extends ConnectionService {
+ // Constants used to address into the mLocks array.
+ public static int CONNECTION_CREATED_LOCK = 0;
+ public static int CREATE_INCOMING_CONNECTION_FAILED_LOCK = 1;
+ public static int CREATE_OUTGOING_CONNECTION_FAILED_LOCK = 2;
+ private static int NUM_LOCKS = CREATE_OUTGOING_CONNECTION_FAILED_LOCK + 1;
+
+ private static CtsSelfManagedConnectionService sConnectionService;
+
+ // Lock used to determine when binding to CS is complete.
+ private static CountDownLatch sBindingLock = new CountDownLatch(1);
+
+ private SelfManagedConnection.Listener mConnectionListener =
+ new SelfManagedConnection.Listener() {
+ @Override
+ void onDestroyed(SelfManagedConnection connection) {
+ mConnections.remove(connection);
+ }
+ };
+
+ private CountDownLatch[] mLocks = new CountDownLatch[NUM_LOCKS];
+
+ private List<SelfManagedConnection> mConnections = new ArrayList<>();
+
+ public static CtsSelfManagedConnectionService getConnectionService() {
+ return sConnectionService;
+ }
+
+ public CtsSelfManagedConnectionService() throws Exception {
+ super();
+ sConnectionService = this;
+ Arrays.setAll(mLocks, i -> new CountDownLatch(1));
+
+ // Inform anyone waiting on binding that we're bound.
+ sBindingLock.countDown();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ sBindingLock = new CountDownLatch(1);
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount,
+ final ConnectionRequest request) {
+
+ return createSelfManagedConnection(request, false);
+ }
+
+ @Override
+ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+
+ return createSelfManagedConnection(request, true);
+ }
+
+ @Override
+ public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ mLocks[CREATE_INCOMING_CONNECTION_FAILED_LOCK].countDown();
+ }
+
+ @Override
+ public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ mLocks[CREATE_OUTGOING_CONNECTION_FAILED_LOCK].countDown();
+ }
+
+ public void tearDown() {
+ if (mConnections != null && mConnections.size() > 0) {
+ mConnections.forEach(connection -> {
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ connection.destroy();
+ }
+ );
+ mConnections.clear();
+ }
+ sBindingLock = new CountDownLatch(1);
+ }
+
+ private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
+ SelfManagedConnection connection = new SelfManagedConnection(isIncoming,
+ mConnectionListener);
+ connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
+ connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
+ connection.setExtras(request.getExtras());
+
+ Bundle moreExtras = new Bundle();
+ moreExtras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ request.getAccountHandle());
+ connection.putExtras(moreExtras);
+ connection.setVideoState(request.getVideoState());
+
+ mConnections.add(connection);
+ mLocks[CONNECTION_CREATED_LOCK].countDown();
+ return connection;
+ }
+
+ public List<SelfManagedConnection> getConnections() {
+ return mConnections;
+ }
+
+ /**
+ * Waits on a lock for maximum of 5 seconds.
+ *
+ * @param lock one of the {@code *_LOCK} constants defined above.
+ * @return {@code true} if the lock was released within the time limit, {@code false} if the
+ * timeout expired without the lock being released.
+ */
+ public boolean waitForUpdate(int lock) {
+ mLocks[lock] = waitForLock(mLocks[lock]);
+ return mLocks[lock] != null;
+ }
+
+ /**
+ * Waits for the {@link ConnectionService} to be found.
+ * @return {@code true} if binding happened within the time limit, or {@code false} otherwise.
+ */
+ public static boolean waitForBinding() {
+ sBindingLock = waitForLock(sBindingLock);
+ return sBindingLock != null;
+ }
+
+ /**
+ * Given a {@link CountDownLatch}, wait for the latch to reach zero for 5 seconds. If the lock
+ * was released, return a new instance. Otherwise, return null to indicate that the timeout
+ * expired without the lock being released.
+ *
+ * @param lock The lock to wait on.
+ * @return {@code true} if the lock was released, and {@code false} if it failed to be released.
+ */
+ private static CountDownLatch waitForLock(CountDownLatch lock) {
+ boolean success;
+ try {
+ success = lock.await(5000, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ie) {
+ return null;
+ }
+
+ if (success) {
+ return new CountDownLatch(1);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java
new file mode 100644
index 0000000..cf1d1a2
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnection.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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 android.telecom.cts;
+
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.cts.BaseTelecomTestWithMockServices.InvokeCounter;
+
+/**
+ * CTS Test self-managed {@link Connection} implementation.
+ */
+public class SelfManagedConnection extends Connection {
+
+ InvokeCounter mCallAudioRouteInvokeCounter = new InvokeCounter("onCallAudioStateChanged");
+ InvokeCounter mOnShowIncomingUiInvokeCounter = new InvokeCounter(
+ "onShowIncomingUiInvokeCounter");
+
+ public static abstract class Listener {
+ void onDestroyed(SelfManagedConnection connection) { };
+ }
+
+ private final boolean mIsIncomingCall;
+ private final Listener mListener;
+
+ public SelfManagedConnection(boolean isIncomingCall, Listener listener) {
+ mIsIncomingCall = isIncomingCall;
+ mListener = listener;
+ }
+
+ public boolean isIncomingCall() {
+ return mIsIncomingCall;
+ }
+
+ public void disconnectAndDestroy() {
+ setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ destroy();
+ mListener.onDestroyed(this);
+ }
+
+ @Override
+ public void onCallAudioStateChanged(CallAudioState state) {
+ mCallAudioRouteInvokeCounter.invoke(state);
+ }
+
+ @Override
+ public void onShowIncomingCallUi() {
+ mOnShowIncomingUiInvokeCounter.invoke();
+ }
+
+ public InvokeCounter getCallAudioStateChangedInvokeCounter() {
+ return mCallAudioRouteInvokeCounter;
+ }
+
+ public InvokeCounter getOnShowIncomingUiInvokeCounter() {
+ return mOnShowIncomingUiInvokeCounter;
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
new file mode 100644
index 0000000..d9bdfa8
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionServiceTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2017 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 android.telecom.cts;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.CallAudioState;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+/**
+ * CTS tests for the self-managed {@link android.telecom.ConnectionService} APIs.
+ * For more information about these APIs, see {@link android.telecom}, and
+ * {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED}.
+ */
+
+public class SelfManagedConnectionServiceTest extends BaseTelecomTestWithMockServices {
+ private Uri TEST_ADDRESS_1 = Uri.fromParts("sip", "call1@test.com", null);
+ private Uri TEST_ADDRESS_2 = Uri.fromParts("sip", "call2@test.com", null);
+ private Uri TEST_ADDRESS_3 = Uri.fromParts("sip", "call3@test.com", null);
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ if (mShouldTestTelecom) {
+ // Register and enable the CTS ConnectionService; we want to be able to test a managed
+ // ConnectionService alongside a self-managed ConnectionService.
+ setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+
+ mTelecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_1);
+ mTelecomManager.registerPhoneAccount(TEST_SELF_MANAGED_PHONE_ACCOUNT_2);
+ }
+
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ CtsSelfManagedConnectionService connectionService =
+ CtsSelfManagedConnectionService.getConnectionService();
+ if (connectionService != null) {
+ connectionService.tearDown();
+ }
+ }
+
+ /**
+ * Tests the ability to successfully register a self-managed
+ * {@link android.telecom.PhoneAccount}.
+ */
+ public void testRegisterSelfManagedConnectionService() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // The phone account is registered in the setup method.
+ assertPhoneAccountRegistered(TEST_SELF_MANAGED_HANDLE_1);
+ assertPhoneAccountEnabled(TEST_SELF_MANAGED_HANDLE_1);
+ PhoneAccount registeredAccount = mTelecomManager.getPhoneAccount(TEST_SELF_MANAGED_HANDLE_1);
+
+ // It should exist and be the same as the previously registered one.
+ assertNotNull(registeredAccount);
+
+ // We cannot just check for equality of the PhoneAccount since the one we registered is not
+ // enabled, and the one we get back after registration is.
+ assertPhoneAccountEquals(TEST_SELF_MANAGED_PHONE_ACCOUNT_1, registeredAccount);
+
+ // An important asumption is that self-managed PhoneAccounts are automatically
+ // enabled by default.
+ assertTrue("Self-managed PhoneAccounts must be enabled by default.",
+ registeredAccount.isEnabled());
+ }
+
+ /**
+ * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+ * but is also registered as a call provider is not permitted.
+ *
+ * A self-managed {@link android.telecom.PhoneAccount} cannot also be a call provider.
+ */
+ public void testRegisterCallCapableSelfManagedConnectionService() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Attempt to register both a call provider and self-managed account.
+ PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .build();
+
+ registerAndExpectFailure(toRegister);
+ }
+
+ /**
+ * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+ * but is also registered as a sim subscription is not permitted.
+ *
+ * A self-managed {@link android.telecom.PhoneAccount} cannot also be a SIM subscription.
+ */
+ public void testRegisterSimSelfManagedConnectionService() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Attempt to register both a call provider and self-managed account.
+ PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ .build();
+
+ registerAndExpectFailure(toRegister);
+ }
+
+ /**
+ * This test ensures that a {@link android.telecom.PhoneAccount} declared as self-managed cannot
+ * but is also registered as a connection manager is not permitted.
+ *
+ * A self-managed {@link android.telecom.PhoneAccount} cannot also be a connection manager.
+ */
+ public void testRegisterConnectionManagerSelfManagedConnectionService() {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Attempt to register both a call provider and self-managed account.
+ PhoneAccount toRegister = TEST_SELF_MANAGED_PHONE_ACCOUNT_1.toBuilder()
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+ .build();
+
+ registerAndExpectFailure(toRegister);
+ }
+
+ /**
+ * Attempts to register a {@link android.telecom.PhoneAccount}, expecting a security exception
+ * which indicates that invalid capabilities were specified.
+ *
+ * @param toRegister The PhoneAccount to register.
+ */
+ private void registerAndExpectFailure(PhoneAccount toRegister) {
+ try {
+ mTelecomManager.registerPhoneAccount(toRegister);
+ } catch (SecurityException se) {
+ assertEquals("Self-managed ConnectionServices cannot also be call capable, " +
+ "connection managers, or SIM accounts.", se.getMessage());
+ return;
+ }
+ fail("Expected SecurityException");
+ }
+
+ /**
+ * Tests ability to add a new self-managed incoming connection.
+ */
+ public void testAddSelfManagedIncomingConnection() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+
+ // Ensure Telecom bound to the self managed CS
+ if (!CtsSelfManagedConnectionService.waitForBinding()) {
+ fail("Could not bind to Self-Managed ConnectionService");
+ }
+
+ SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+
+ // Expect callback indicating that UI should be shown.
+ connection.getOnShowIncomingUiInvokeCounter().waitForCount(1);
+
+ setActiveAndVerify(connection);
+ assertMockInCallServiceUnbound();
+ setDisconnectedAndVerify(connection);
+ }
+
+ /**
+ * Tests ability to add a new self-managed outgoing connection.
+ */
+ public void testAddSelfManagedOutgoingConnection() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+
+ // Ensure Telecom bound to the self managed CS
+ if (!CtsSelfManagedConnectionService.waitForBinding()) {
+ fail("Could not bind to Self-Managed ConnectionService");
+ }
+
+ SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+ assert(!connection.isIncomingCall());
+
+ assertEquals(connection.getOnShowIncomingUiInvokeCounter().getInvokeCount(), 0);
+
+ setActiveAndVerify(connection);
+ assertMockInCallServiceUnbound();
+ setDisconnectedAndVerify(connection);
+ }
+
+ /**
+ * Tests ability to change the audio route via the
+ * {@link android.telecom.Connection#setAudioRoute(int)} API.
+ */
+ public void testAudioRoute() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+ SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+ setActiveAndVerify(connection);
+
+ InvokeCounter counter = connection.getCallAudioStateChangedInvokeCounter();
+
+ connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+ counter.waitForPredicate(new Predicate<CallAudioState>() {
+ @Override
+ public boolean test(CallAudioState cas) {
+ return cas.getRoute() == CallAudioState.ROUTE_SPEAKER;
+ }
+ }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ connection.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+ counter.waitForPredicate(new Predicate<CallAudioState>() {
+ @Override
+ public boolean test(CallAudioState cas) {
+ return cas.getRoute() == CallAudioState.ROUTE_EARPIECE;
+ }
+ }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+ setDisconnectedAndVerify(connection);
+ }
+
+ /**
+ * Tests that Telecom will disallow an outgoing call when there is already an ongoing call in
+ * another third-party app.
+ * @throws Exception
+ */
+ public void testDisallowOutgoingCall() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Create an ongoing call in the first self-managed PhoneAccount.
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+ SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+ setActiveAndVerify(connection);
+
+ // Attempt to create a new outgoing call for the other PhoneAccount; it should fail.
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_2);
+ assertTrue("Expected onCreateOutgoingConnectionFailed callback",
+ CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+ CtsSelfManagedConnectionService.CREATE_OUTGOING_CONNECTION_FAILED_LOCK));
+
+ setDisconnectedAndVerify(connection);
+ }
+
+ /**
+ * Tests that Telecom will disallow an outgoing call when there is already an ongoing call in
+ * another third-party app.
+ * @throws Exception
+ */
+ public void testIncomingWhileOngoing() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Create an ongoing call in the first self-managed PhoneAccount.
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+ SelfManagedConnection connection = waitForAndGetConnection(TEST_ADDRESS_1);
+ setActiveAndVerify(connection);
+
+ // Attempt to create a new outgoing call for the other PhoneAccount; it should succeed.
+ addIncomingCall(TEST_SELF_MANAGED_HANDLE_2, TEST_ADDRESS_2);
+ SelfManagedConnection connection2 = waitForAndGetConnection(TEST_ADDRESS_2);
+
+ connection2.disconnectAndDestroy();
+ setDisconnectedAndVerify(connection);
+ }
+
+ /**
+ * Tests that Telecom enforces a maximum number of calls for a self-managed ConnectionService.
+ *
+ * @throws Exception
+ */
+ public void testCallLimit() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ List<SelfManagedConnection> connections = new ArrayList<>();
+ // Create 10 calls; they should succeed.
+ for (int ix = 0; ix < 10; ix++) {
+ Uri address = Uri.fromParts("sip", "test" + ix + "@test.com", null);
+ // Create an ongoing call in the first self-managed PhoneAccount.
+ placeOutgoingCall(TEST_SELF_MANAGED_HANDLE_1, address);
+ SelfManagedConnection connection = waitForAndGetConnection(address);
+ setActiveAndVerify(connection);
+ connections.add(connection);
+ }
+
+ // Try adding an 11th. It should fail to be created.
+ addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_2);
+ assertTrue("Expected onCreateIncomingConnectionFailed callback",
+ CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+ CtsSelfManagedConnectionService.CREATE_INCOMING_CONNECTION_FAILED_LOCK));
+
+ connections.forEach((selfManagedConnection) ->
+ selfManagedConnection.disconnectAndDestroy());
+ }
+
+ public void testEmergencyCallOngoing() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ // Set 555-1212 as a test emergency number.
+ TestUtils.executeShellCommand(getInstrumentation(),
+ "setprop ril.ecclist 5551212");
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_PHONE_ACCOUNT_HANDLE);
+ mTelecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, "5551212", null), extras);
+ assertIsInCall(true);
+ try {
+ TestUtils.waitOnAllHandlers(getInstrumentation());
+ } catch (Exception e) {
+ fail("Failed to wait on handlers " + e);
+ }
+
+ // Try adding a self managed call. It should fail to be created.
+ addIncomingCall(TEST_SELF_MANAGED_HANDLE_1, TEST_ADDRESS_1);
+ assertTrue("Expected onCreateIncomingConnectionFailed callback",
+ CtsSelfManagedConnectionService.getConnectionService().waitForUpdate(
+ CtsSelfManagedConnectionService.CREATE_INCOMING_CONNECTION_FAILED_LOCK));
+ }
+
+ /**
+ * Adds a new self-managed incoming call.
+ *
+ * @param handle the PhoneAccountHandle associated with the call.
+ * @param address the incoming address.
+ * @return the new self-managed incoming call.
+ */
+ private void addIncomingCall(PhoneAccountHandle handle, Uri address) {
+ // Inform telecom of new incoming self-managed connection.
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, address);
+ mTelecomManager.addNewIncomingCall(handle, extras);
+
+ // Wait for Telecom to finish creating the new connection.
+ try {
+ TestUtils.waitOnAllHandlers(getInstrumentation());
+ } catch (Exception e) {
+ fail("Failed to wait on handlers");
+ }
+ }
+
+ /**
+ * Places a new self-managed outgoing call.
+ *
+ * @param handle the PhoneAccountHandle associated with the call.
+ * @param address outgoing call address.
+ * @return the new self-managed outgoing call.
+ */
+ private void placeOutgoingCall(PhoneAccountHandle handle, Uri address) {
+ // Inform telecom of new incoming self-managed connection.
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle);
+ mTelecomManager.placeCall(address, extras);
+
+ // Wait for Telecom to finish creating the new connection.
+ try {
+ TestUtils.waitOnAllHandlers(getInstrumentation());
+ } catch (Exception e) {
+ fail("Failed to wait on handlers");
+ }
+ }
+
+ /**
+ * Waits for a new SelfManagedConnection with the given address to be added.
+ * @param address The expected address.
+ * @return The SelfManagedConnection found.
+ */
+ public SelfManagedConnection waitForAndGetConnection(Uri address) {
+ // Wait for creation of the new connection.
+ CtsSelfManagedConnectionService connectionService =
+ CtsSelfManagedConnectionService.getConnectionService();
+ assertTrue(connectionService.waitForUpdate(
+ CtsSelfManagedConnectionService.CONNECTION_CREATED_LOCK));
+
+ Optional<SelfManagedConnection> connectionOptional = connectionService.getConnections()
+ .stream()
+ .filter(connection -> address.equals(connection.getAddress()))
+ .findFirst();
+
+ assert(connectionOptional.isPresent());
+ return connectionOptional.get();
+ }
+
+ /**
+ * Sets a connection active, verifies TelecomManager thinks we're in call, that we did not bind
+ * to the IncallService, and finally disconnects and destroys the connection..
+ * @param connection The connection.
+ */
+ private void setActiveAndVerify(SelfManagedConnection connection) throws Exception {
+ // Set the connection active.
+ connection.setActive();
+
+ // Check with Telecom if we're in a call.
+ assertIsInCall(true);
+ }
+
+ /**
+ * Sets a connection to be disconnected, and then waits until the TelecomManager reports it is
+ * no longer in a call.
+ *
+ * @param connection The connection to disconnect/destroy.
+ */
+ private void setDisconnectedAndVerify(SelfManagedConnection connection) {
+ // Now, disconnect call and clean it up.
+ connection.disconnectAndDestroy();
+
+ assertIsInCall(false);
+ }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index f259985..030ee77 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -41,12 +41,17 @@
// tests in the Telecom2 test package.
public static String PACKAGE = "android.telecom.cts";
public static final String COMPONENT = "android.telecom.cts.CtsConnectionService";
+ public static final String SELF_MANAGED_COMPONENT =
+ "android.telecom.cts.CtsSelfManagedConnectionService";
public static final String REMOTE_COMPONENT = "android.telecom.cts.CtsRemoteConnectionService";
public static final String ACCOUNT_ID = "xtstest_CALL_PROVIDER_ID";
public static final String REMOTE_ACCOUNT_ID = "xtstest_REMOTE_CALL_PROVIDER_ID";
+ public static final String SELF_MANAGED_ACCOUNT_ID_1 = "ctstest_SELF_MANAGED_ID_1";
+ public static final String SELF_MANAGED_ACCOUNT_ID_2 = "ctstest_SELF_MANAGED_ID_2";
public static final String ACCOUNT_LABEL = "CTSConnectionService";
public static final String REMOTE_ACCOUNT_LABEL = "CTSRemoteConnectionService";
+ public static final String SELF_MANAGED_ACCOUNT_LABEL = "CtsSelfManagedConnectionService";
private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
@@ -58,6 +63,8 @@
private static final String COMMAND_REGISTER_SIM = "telecom register-sim-phone-account ";
+ private static final String COMMAND_WAIT_ON_HANDLERS = "telecom wait-on-handlers";
+
public static final String MERGE_CALLER_NAME = "calls-merged";
public static final String SWAP_CALLER_NAME = "calls-swapped";
private static final String PRIMARY_USER_SN = "0";
@@ -100,6 +107,10 @@
+ handle.getId() + " " + PRIMARY_USER_SN + " " + label + " " + address);
}
+ public static void waitOnAllHandlers(Instrumentation instrumentation) throws Exception {
+ executeShellCommand(instrumentation, COMMAND_WAIT_ON_HANDLERS);
+ }
+
/**
* Executes the given shell command and returns the output in a string. Note that even
* if we don't care about the output, we have to read the stream completely to make the
diff --git a/tests/tests/text/src/android/text/cts/EmojiTest.java b/tests/tests/text/src/android/text/cts/EmojiTest.java
index 2e5bbad..e841fb5 100644
--- a/tests/tests/text/src/android/text/cts/EmojiTest.java
+++ b/tests/tests/text/src/android/text/cts/EmojiTest.java
@@ -184,7 +184,7 @@
}
}
- private class CaptureCanvas extends View {
+ private static class CaptureCanvas extends View {
String mTestStr;
Paint paint = new Paint();
@@ -216,7 +216,7 @@
}
- private class CaptureTextView extends TextView {
+ private static class CaptureTextView extends TextView {
CaptureTextView(Context context) {
super(context);
@@ -240,7 +240,7 @@
}
- private class CaptureEditText extends EditText {
+ private static class CaptureEditText extends EditText {
CaptureEditText(Context context) {
super(context);
diff --git a/tests/tests/text/src/android/text/cts/FontManagerTest.java b/tests/tests/text/src/android/text/cts/FontManagerTest.java
index 69ea522..1e3631a 100644
--- a/tests/tests/text/src/android/text/cts/FontManagerTest.java
+++ b/tests/tests/text/src/android/text/cts/FontManagerTest.java
@@ -15,64 +15,76 @@
*/
package android.text.cts;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.text.FontConfig;
import android.text.FontManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileDescriptor;
import java.util.List;
/**
* Tests {@link FontManager}.
*/
-public class FontManagerTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontManagerTest {
private FontManager mFontManager;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
- mFontManager = (FontManager) getContext().getSystemService(Context.FONT_SERVICE);
+ final Context targetContext = InstrumentationRegistry.getTargetContext();
+ mFontManager = (FontManager) targetContext.getSystemService(Context.FONT_SERVICE);
}
- @SmallTest
+ @Test
public void testGetSystemFontsData() {
- FontConfig config = mFontManager.getSystemFonts();
+ final FontConfig config = mFontManager.getSystemFonts();
assertNotNull(config);
assertTrue("There should at least be one font family", config.getFamilies().size() > 0);
for (int i = 0; i < config.getFamilies().size(); ++i) {
- FontConfig.Family family = config.getFamilies().get(i);
+ final FontConfig.Family family = config.getFamilies().get(i);
assertTrue("Each font family should have at least one font",
family.getFonts().size() > 0);
for (int j = 0; j < family.getFonts().size(); ++j) {
- FontConfig.Font font = family.getFonts().get(j);
+ final FontConfig.Font font = family.getFonts().get(j);
assertNotNull("FontManager should provide a FileDescriptor for each system font",
font.getFd());
}
}
}
- @SmallTest
+ @Test
public void testFileDescriptorsAreReadOnly() throws Exception {
- FontConfig fc = mFontManager.getSystemFonts();
+ final FontConfig fc = mFontManager.getSystemFonts();
- List<FontConfig.Family> families = fc.getFamilies();
+ final List<FontConfig.Family> families = fc.getFamilies();
for (int i = 0; i < families.size(); ++i) {
- List<FontConfig.Font> fonts = families.get(i).getFonts();
+ final List<FontConfig.Font> fonts = families.get(i).getFonts();
for (int j = 0; j < fonts.size(); ++j) {
- ParcelFileDescriptor pfd = fonts.get(j).getFd();
+ final ParcelFileDescriptor pfd = fonts.get(j).getFd();
assertNotNull(pfd);
- FileDescriptor fd = pfd.getFileDescriptor();
+ final FileDescriptor fd = pfd.getFileDescriptor();
long size = Os.lseek(fd, 0, OsConstants.SEEK_END);
// Read only mapping should success.
- long addr = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, 0);
+ final long addr = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_SHARED,
+ fd, 0);
Os.munmap(addr, size);
// Mapping with PROT_WRITE should fail with EPERM.
diff --git a/tests/tests/text/src/android/text/cts/LayoutTest.java b/tests/tests/text/src/android/text/cts/LayoutTest.java
index d94fb23..6b6c4fc 100644
--- a/tests/tests/text/src/android/text/cts/LayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/LayoutTest.java
@@ -323,7 +323,7 @@
assertTrue(widthLongest > widthLonger);
}
- private final class MockLayout extends Layout {
+ private static final class MockLayout extends Layout {
public MockLayout(CharSequence text, TextPaint paint, int width,
Alignment align, float spacingmult, float spacingadd) {
super(text, paint, width, align, spacingmult, spacingadd);
diff --git a/tests/tests/text/src/android/text/cts/MyanmarTest.java b/tests/tests/text/src/android/text/cts/MyanmarTest.java
index 5ded363..8a71adb 100644
--- a/tests/tests/text/src/android/text/cts/MyanmarTest.java
+++ b/tests/tests/text/src/android/text/cts/MyanmarTest.java
@@ -60,7 +60,7 @@
}
}
- private class CaptureTextView extends TextView {
+ private static class CaptureTextView extends TextView {
CaptureTextView(Context context) {
super(context);
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java b/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
index f693038..8ca7598 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringBuilderSpanTest.java
@@ -401,7 +401,7 @@
private Spannable mSpannable;
- private class AddedRemoved {
+ private static class AddedRemoved {
Object span;
int start;
int end;
@@ -413,7 +413,7 @@
}
}
- private class Changed {
+ private static class Changed {
Object span;
int oldStart;
int oldEnd;
diff --git a/tests/tests/text/src/android/text/cts/SpannableStringTest.java b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
index 81dbf9b..229bea6 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
@@ -16,19 +16,31 @@
package android.text.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
import android.support.test.filters.SmallTest;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.NoCopySpan;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.LocaleSpan;
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Locale;
-public class SpannableStringTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SpannableStringTest {
- @SmallTest
+ @Test
public void testConstructor() {
new SpannableString("test");
@@ -39,7 +51,7 @@
}
}
- @SmallTest
+ @Test
public void testValueOf() {
String text = "test valueOf";
SpannableString spannable = SpannableString.valueOf(text);
@@ -56,7 +68,7 @@
}
}
- @SmallTest
+ @Test
public void testSetSpan() {
String text = "hello, world";
SpannableString spannable = new SpannableString(text);
@@ -87,7 +99,7 @@
}
}
- @SmallTest
+ @Test
public void testRemoveSpan() {
SpannableString spannable = new SpannableString("hello, world");
@@ -111,7 +123,7 @@
assertEquals(0, spannable.getSpanFlags(underlineSpan));
}
- @SmallTest
+ @Test
public void testSubSequence() {
String text = "hello, world";
SpannableString spannable = new SpannableString(text);
@@ -135,7 +147,7 @@
}
}
- @SmallTest
+ @Test
public void testSubsequence_copiesSpans() {
SpannableString first = new SpannableString("t\nest data");
QuoteSpan quoteSpan = new QuoteSpan();
@@ -166,7 +178,7 @@
}
- @SmallTest
+ @Test
public void testCopyConstructor_copiesAllSpans() {
SpannableString first = new SpannableString("t\nest data");
first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
@@ -189,7 +201,24 @@
}
}
- @SmallTest
+ @Test
+ public void testCopyConstructor_doesNotCopyNoCopySpans() {
+ final SpannableString first = new SpannableString("t\nest data");
+ first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+ first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
+
+ final SpannableString copied = new SpannableString(first);
+ final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
+ assertNotNull(spans);
+ assertEquals(2, spans.length);
+
+ for (int i = 0; i < spans.length; i++) {
+ assertFalse(spans[i] instanceof NoCopySpan);
+ }
+ }
+
+ @Test
public void testCopyGrowable() {
SpannableString first = new SpannableString("t\nest data");
final int N_SPANS = 127;
diff --git a/tests/tests/text/src/android/text/cts/SpannedStringTest.java b/tests/tests/text/src/android/text/cts/SpannedStringTest.java
index ccdb119..b20469e 100644
--- a/tests/tests/text/src/android/text/cts/SpannedStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannedStringTest.java
@@ -17,13 +17,20 @@
package android.text.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.NoCopySpan;
+import android.text.SpannableString;
+import android.text.Spanned;
import android.text.SpannedString;
+import android.text.style.QuoteSpan;
+import android.text.style.UnderlineSpan;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,4 +88,22 @@
} catch (StringIndexOutOfBoundsException e) {
}
}
+
+ @Test
+ public void testCopyConstructor_doesNotCopyNoCopySpans() {
+ final SpannableString first = new SpannableString("t\nest data");
+ first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+ first.setSpan(new NoCopySpan.Concrete(), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
+
+ final SpannedString copied = new SpannedString(first);
+ final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
+ assertNotNull(spans);
+ assertEquals(2, spans.length);
+
+ for (int i = 0; i < spans.length; i++) {
+ assertFalse(spans[i] instanceof NoCopySpan);
+ }
+ }
+
}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index da51c74..d1e3ac3 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -76,8 +76,8 @@
private StaticLayout mDefaultLayout;
private TextPaint mDefaultPaint;
- private class TestingTextPaint extends TextPaint {
- // need to have a subclass to insure measurement happens in Java and not C++
+ private static class TestingTextPaint extends TextPaint {
+ // need to have a subclass to ensure measurement happens in Java and not C++
}
@Before
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 4d634fb..979dc15 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -979,7 +979,7 @@
/**
* MockGetChars for test.
*/
- private class MockGetChars implements GetChars {
+ private static class MockGetChars implements GetChars {
private boolean mHasCalledGetChars;
private GetCharsParams mGetCharsParams = new GetCharsParams();
@@ -1026,7 +1026,7 @@
/**
* MockCharSequence for test.
*/
- private class MockCharSequence implements CharSequence {
+ private static class MockCharSequence implements CharSequence {
private char mText[];
public MockCharSequence(String text) {
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java b/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java
new file mode 100644
index 0000000..1438788
--- /dev/null
+++ b/tests/tests/transition/src/android/transition/cts/TransitionListenerAdapterTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 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 android.transition.cts;
+
+import android.transition.TransitionListenerAdapter;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransitionListenerAdapterTest {
+ /**
+ * TransitionListenerAdapter has a noop implementation of the TransitionListener interface.
+ * It should do nothing, including when nulls are passed to it.
+ * <p>
+ * Mostly this test pokes the implementation so that it is counted as tested. There isn't
+ * much to test here since it has no implementation.
+ */
+ @Test
+ public void testNullOk() {
+ TransitionListenerAdapter adapter = new MyAdapter();
+ adapter.onTransitionStart(null);
+ adapter.onTransitionEnd(null);
+ adapter.onTransitionCancel(null);
+ adapter.onTransitionPause(null);
+ adapter.onTransitionResume(null);
+ }
+
+ private static class MyAdapter extends TransitionListenerAdapter {
+ }
+}
diff --git a/tests/tests/transition/src/android/transition/cts/TransitionTest.java b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
index 506ea7b..b60f1ca 100644
--- a/tests/tests/transition/src/android/transition/cts/TransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/TransitionTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -443,7 +444,7 @@
assertFalse(transition.animators.isEmpty());
Animator animator = transition.animators.get(redSquare);
Animator.AnimatorListener listener = transition.listeners.get(redSquare);
- verify(listener, within(100)).onAnimationStart(any());
+ verify(listener, within(100)).onAnimationStart(any(), eq(false));
assertSame(interpolator, animator.getInterpolator());
endTransition();
}
@@ -500,7 +501,7 @@
Animator redSquareAnimator = transition.animators.get(redSquare);
Animator greenSquareAnimator = transition.animators.get(greenSquare);
Animator.AnimatorListener listener = transition.listeners.get(redSquare);
- verify(listener, within(100)).onAnimationStart(any());
+ verify(listener, within(100)).onAnimationStart(any(), eq(false));
assertEquals(0, redSquareAnimator.getStartDelay());
assertEquals(diffTop, greenSquareAnimator.getStartDelay());
endTransition();
@@ -532,7 +533,7 @@
Animator animator = transition.animators.get(redSquare);
assertFalse(animator.isRunning());
Animator.AnimatorListener listener = transition.listeners.get(redSquare);
- verify(listener, within(250)).onAnimationStart(any());
+ verify(listener, within(250)).onAnimationStart(any(), eq(false));
endTransition();
}
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 6cd4f4e..708477a 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -27,6 +27,7 @@
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import android.media.tv.TvContract.Channels;
+import android.media.tv.TvContract.Programs;
import android.media.tv.TvContract.Programs.Genres;
import android.media.tv.TvContract.RecordedPrograms;
import android.net.Uri;
@@ -44,44 +45,44 @@
*/
public class TvContractTest extends AndroidTestCase {
private static final String[] CHANNELS_PROJECTION = {
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_INPUT_ID,
- TvContract.Channels.COLUMN_TYPE,
- TvContract.Channels.COLUMN_SERVICE_TYPE,
- TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID,
- TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID,
- TvContract.Channels.COLUMN_SERVICE_ID,
- TvContract.Channels.COLUMN_DISPLAY_NUMBER,
- TvContract.Channels.COLUMN_DISPLAY_NAME,
- TvContract.Channels.COLUMN_NETWORK_AFFILIATION,
- TvContract.Channels.COLUMN_DESCRIPTION,
- TvContract.Channels.COLUMN_VIDEO_FORMAT,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
- TvContract.Channels.COLUMN_VERSION_NUMBER,
+ Channels._ID,
+ Channels.COLUMN_INPUT_ID,
+ Channels.COLUMN_TYPE,
+ Channels.COLUMN_SERVICE_TYPE,
+ Channels.COLUMN_ORIGINAL_NETWORK_ID,
+ Channels.COLUMN_TRANSPORT_STREAM_ID,
+ Channels.COLUMN_SERVICE_ID,
+ Channels.COLUMN_DISPLAY_NUMBER,
+ Channels.COLUMN_DISPLAY_NAME,
+ Channels.COLUMN_NETWORK_AFFILIATION,
+ Channels.COLUMN_DESCRIPTION,
+ Channels.COLUMN_VIDEO_FORMAT,
+ Channels.COLUMN_INTERNAL_PROVIDER_DATA,
+ Channels.COLUMN_VERSION_NUMBER,
};
private static final String[] PROGRAMS_PROJECTION = {
- TvContract.Programs._ID,
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_SEASON_TITLE,
- TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_EPISODE_TITLE,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_BROADCAST_GENRE,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_LONG_DESCRIPTION,
- TvContract.Programs.COLUMN_VIDEO_WIDTH,
- TvContract.Programs.COLUMN_VIDEO_HEIGHT,
- TvContract.Programs.COLUMN_AUDIO_LANGUAGE,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_POSTER_ART_URI,
- TvContract.Programs.COLUMN_THUMBNAIL_URI,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
- TvContract.Programs.COLUMN_VERSION_NUMBER,
+ Programs._ID,
+ Programs.COLUMN_CHANNEL_ID,
+ Programs.COLUMN_TITLE,
+ Programs.COLUMN_SEASON_DISPLAY_NUMBER,
+ Programs.COLUMN_SEASON_TITLE,
+ Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
+ Programs.COLUMN_EPISODE_TITLE,
+ Programs.COLUMN_START_TIME_UTC_MILLIS,
+ Programs.COLUMN_END_TIME_UTC_MILLIS,
+ Programs.COLUMN_BROADCAST_GENRE,
+ Programs.COLUMN_CANONICAL_GENRE,
+ Programs.COLUMN_SHORT_DESCRIPTION,
+ Programs.COLUMN_LONG_DESCRIPTION,
+ Programs.COLUMN_VIDEO_WIDTH,
+ Programs.COLUMN_VIDEO_HEIGHT,
+ Programs.COLUMN_AUDIO_LANGUAGE,
+ Programs.COLUMN_CONTENT_RATING,
+ Programs.COLUMN_POSTER_ART_URI,
+ Programs.COLUMN_THUMBNAIL_URI,
+ Programs.COLUMN_INTERNAL_PROVIDER_DATA,
+ Programs.COLUMN_VERSION_NUMBER,
};
private static long OPERATION_TIME = 1000l;
@@ -101,6 +102,7 @@
private static final String WHITE_SPACES = " \r \n \t \f ";
private static final String PARAM_CANONICAL_GENRE = "canonical_genre";
+ private static final String NON_EXISTING_COLUMN_NAME = "non_existing_column";
private String mInputId;
private ContentResolver mContentResolver;
@@ -127,7 +129,7 @@
// Clean up, just in case we failed to delete the entry when a test failed.
// The cotentUris are specific to this package, so this will delete only the
// entries inserted by this package.
- String[] projection = { TvContract.Channels._ID };
+ String[] projection = { Channels._ID };
try (Cursor cursor = mContentResolver.query(mChannelsUri, projection, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
long channelId = cursor.getLong(0);
@@ -142,92 +144,106 @@
private static ContentValues createDummyChannelValues(String inputId, boolean preview) {
ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
- values.put(TvContract.Channels.COLUMN_TYPE,
- preview ? TvContract.Channels.TYPE_PREVIEW : TvContract.Channels.TYPE_OTHER);
- values.put(TvContract.Channels.COLUMN_SERVICE_TYPE,
- TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO);
- values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "1");
- values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, TvContract.Channels.VIDEO_FORMAT_480P);
+ values.put(Channels.COLUMN_INPUT_ID, inputId);
+ values.put(Channels.COLUMN_TYPE, preview ? Channels.TYPE_PREVIEW : Channels.TYPE_OTHER);
+ values.put(Channels.COLUMN_SERVICE_TYPE, Channels.SERVICE_TYPE_AUDIO_VIDEO);
+ values.put(Channels.COLUMN_DISPLAY_NUMBER, "1");
+ values.put(Channels.COLUMN_VIDEO_FORMAT, Channels.VIDEO_FORMAT_480P);
return values;
}
private static ContentValues createDummyProgramValues(long channelId) {
ContentValues values = new ContentValues();
- values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
- values.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
- values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "episode_title");
- values.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
- values.put(TvContract.Programs.COLUMN_SEASON_TITLE, "season_title");
- values.put(TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(
- TvContract.Programs.Genres.MOVIES, TvContract.Programs.Genres.DRAMA));
+ values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+ values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
+ values.put(Programs.COLUMN_EPISODE_TITLE, "episode_title");
+ values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
+ values.put(Programs.COLUMN_SEASON_TITLE, "season_title");
+ values.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
+ Programs.Genres.MOVIES, Programs.Genres.DRAMA));
TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
"US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
- values.put(TvContract.Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+ values.put(Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
return values;
}
private static ContentValues createDummyPreviewProgramValues(long channelId) {
ContentValues values = new ContentValues();
- values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
- values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
- values.put(TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
- values.put(TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION, 5000);
- values.put(TvContract.Programs.COLUMN_PREVIEW_DURATION, 60000);
- values.put(TvContract.Programs.COLUMN_PREVIEW_INTENT_URI,
- "preview_app_link_intent");
- values.put(TvContract.Programs.COLUMN_PREVIEW_WEIGHT, 100);
- values.put(TvContract.Programs.COLUMN_TITLE, "program_title");
- values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "short_description");
- values.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
- values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "episode_title");
- values.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
- values.put(TvContract.Programs.COLUMN_SEASON_TITLE, "season_title");
- values.put(TvContract.Programs.COLUMN_CANONICAL_GENRE, TvContract.Programs.Genres.encode(
- TvContract.Programs.Genres.SPORTS, TvContract.Programs.Genres.DRAMA));
+ values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+ values.put(Programs.COLUMN_INTERNAL_PROVIDER_ID, "ID-4321");
+ values.put(Programs.COLUMN_PREVIEW_VIDEO_URI, "http://test.com/preview.mp4");
+ values.put(Programs.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, 5000);
+ values.put(Programs.COLUMN_DURATION_MILLIS, 60000);
+ values.put(Programs.COLUMN_APP_LINK_INTENT_URI, "app_link_intent");
+ values.put(Programs.COLUMN_WEIGHT, 100);
+ values.put(Programs.COLUMN_TITLE, "program_title");
+ values.put(Programs.COLUMN_SHORT_DESCRIPTION, "short_description");
+ values.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER , "1A");
+ values.put(Programs.COLUMN_EPISODE_TITLE, "episode_title");
+ values.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER , "2B");
+ values.put(Programs.COLUMN_SEASON_TITLE, "season_title");
+ values.put(Programs.COLUMN_CANONICAL_GENRE, Programs.Genres.encode(
+ Programs.Genres.SPORTS, Programs.Genres.DRAMA));
TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
"US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
- values.put(TvContract.Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+ values.put(Programs.COLUMN_CONTENT_RATING, rating.flattenToString());
+ values.put(Programs.COLUMN_TYPE, Programs.TYPE_MOVIE);
+ values.put(Programs.COLUMN_WATCH_NEXT_TYPE, Programs.WATCH_NEXT_TYPE_CONTINUE);
+ values.put(Programs.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
+ values.put(Programs.COLUMN_POSTER_ART_ASPECT_RATIO, Programs.ASPECT_RATIO_2_3);
+ values.put(Programs.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
+ values.put(Programs.COLUMN_THUMBNAIL_ASPECT_RATIO, Programs.ASPECT_RATIO_16_9);
+ values.put(Programs.COLUMN_LOGO_URI, "http://foo.com/logo.jpg");
+ values.put(Programs.COLUMN_AVAILABILITY, Programs.AVAILABILITY_AVAILABLE);
+ values.put(Programs.COLUMN_STARTING_PRICE, "10.99 USD");
+ values.put(Programs.COLUMN_OFFER_PRICE, "3.99 USD");
+ values.put(Programs.COLUMN_RELEASE_DATE, "1985");
+ values.put(Programs.COLUMN_ITEM_COUNT, 1);
+ values.put(Programs.COLUMN_LIVE, 0);
+ values.put(Programs.COLUMN_INTERACTION_TYPE, Programs.INTERACTION_TYPE_LIKES);
+ values.put(Programs.COLUMN_INTERACTION_COUNT, 4000);
+ values.put(Programs.COLUMN_AUTHOR, "author_name1");
+ values.put(Programs.COLUMN_REVIEW_RATING_STYLE, Programs.REVIEW_RATING_STYLE_STARS);
+ values.put(Programs.COLUMN_REVIEW_RATING, "4.5");
return values;
}
private static ContentValues createDummyRecordedProgramValues(String inputId, long channelId) {
ContentValues values = new ContentValues();
- values.put(TvContract.RecordedPrograms.COLUMN_INPUT_ID, inputId);
- values.put(TvContract.RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
- values.put(TvContract.RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "3B");
- values.put(TvContract.RecordedPrograms.COLUMN_SEASON_TITLE, "season_title");
- values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "2A");
- values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title");
- values.put(TvContract.RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 1000);
- values.put(TvContract.RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 2000);
- values.put(TvContract.RecordedPrograms.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.Genres.encode(
- TvContract.Programs.Genres.MOVIES, TvContract.Programs.Genres.DRAMA));
- values.put(TvContract.RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
- values.put(TvContract.RecordedPrograms.COLUMN_LONG_DESCRIPTION, "long_description");
- values.put(TvContract.RecordedPrograms.COLUMN_VIDEO_WIDTH, 1920);
- values.put(TvContract.RecordedPrograms.COLUMN_VIDEO_HEIGHT, 1080);
- values.put(TvContract.RecordedPrograms.COLUMN_AUDIO_LANGUAGE, "en");
+ values.put(RecordedPrograms.COLUMN_INPUT_ID, inputId);
+ values.put(RecordedPrograms.COLUMN_CHANNEL_ID, channelId);
+ values.put(RecordedPrograms.COLUMN_SEASON_DISPLAY_NUMBER , "3B");
+ values.put(RecordedPrograms.COLUMN_SEASON_TITLE, "season_title");
+ values.put(RecordedPrograms.COLUMN_EPISODE_DISPLAY_NUMBER , "2A");
+ values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title");
+ values.put(RecordedPrograms.COLUMN_START_TIME_UTC_MILLIS, 1000);
+ values.put(RecordedPrograms.COLUMN_END_TIME_UTC_MILLIS, 2000);
+ values.put(RecordedPrograms.COLUMN_CANONICAL_GENRE,
+ Programs.Genres.encode(Programs.Genres.MOVIES, Programs.Genres.DRAMA));
+ values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description");
+ values.put(RecordedPrograms.COLUMN_LONG_DESCRIPTION, "long_description");
+ values.put(RecordedPrograms.COLUMN_VIDEO_WIDTH, 1920);
+ values.put(RecordedPrograms.COLUMN_VIDEO_HEIGHT, 1080);
+ values.put(RecordedPrograms.COLUMN_AUDIO_LANGUAGE, "en");
TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
"US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
- values.put(TvContract.RecordedPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
- values.put(TvContract.RecordedPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
- values.put(TvContract.RecordedPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
- values.put(TvContract.RecordedPrograms.COLUMN_SEARCHABLE, 1);
- values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI, "file:///sdcard/foo/");
- values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 1024 * 1024);
- values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 60 * 60 * 1000);
- values.put(TvContract.RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 1454480880L);
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ values.put(RecordedPrograms.COLUMN_CONTENT_RATING, rating.flattenToString());
+ values.put(RecordedPrograms.COLUMN_POSTER_ART_URI, "http://foo.com/artwork.png");
+ values.put(RecordedPrograms.COLUMN_THUMBNAIL_URI, "http://foo.com/thumbnail.jpg");
+ values.put(RecordedPrograms.COLUMN_SEARCHABLE, 1);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_URI, "file:///sdcard/foo/");
+ values.put(RecordedPrograms.COLUMN_RECORDING_DATA_BYTES, 1024 * 1024);
+ values.put(RecordedPrograms.COLUMN_RECORDING_DURATION_MILLIS, 60 * 60 * 1000);
+ values.put(RecordedPrograms.COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS, 1454480880L);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
"internal_provider_data".getBytes());
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 4);
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 3);
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 2);
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 1);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG1, 4);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2, 3);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG3, 2);
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_FLAG4, 1);
return values;
}
@@ -274,25 +290,35 @@
assertNotNull(cursor);
assertEquals(cursor.getCount(), 1);
assertTrue(cursor.moveToNext());
- assertEquals(channelId, cursor.getLong(cursor.getColumnIndex(TvContract.Channels._ID)));
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_INPUT_ID);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_TYPE);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_SERVICE_TYPE);
- verifyIntegerColumn(cursor, expectedValues,
- TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID);
- verifyIntegerColumn(cursor, expectedValues,
- TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID);
- verifyIntegerColumn(cursor, expectedValues,
- TvContract.Channels.COLUMN_SERVICE_ID);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DISPLAY_NUMBER);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DISPLAY_NAME);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Channels.COLUMN_NETWORK_AFFILIATION);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_DESCRIPTION);
- verifyStringColumn(cursor, expectedValues, TvContract.Channels.COLUMN_VIDEO_FORMAT);
- verifyBlobColumn(cursor, expectedValues,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Channels.COLUMN_VERSION_NUMBER);
+ assertEquals(channelId, cursor.getLong(cursor.getColumnIndex(Channels._ID)));
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INPUT_ID);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_TYPE);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_TYPE);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_ORIGINAL_NETWORK_ID);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_TRANSPORT_STREAM_ID);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_SERVICE_ID);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NUMBER);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DISPLAY_NAME);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_NETWORK_AFFILIATION);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_DESCRIPTION);
+ verifyStringColumn(cursor, expectedValues, Channels.COLUMN_VIDEO_FORMAT);
+ verifyBlobColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_DATA);
+ verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_VERSION_NUMBER);
+ }
+ }
+
+ private void verifyNonExistingColumn(Uri channelUri, long channelId) {
+ String[] projection = {
+ Channels._ID,
+ NON_EXISTING_COLUMN_NAME
+ };
+ try (Cursor cursor = mContentResolver.query(channelUri, projection, null, null, null)) {
+ assertNotNull(cursor);
+ assertEquals(cursor.getCount(), 1);
+ assertTrue(cursor.moveToNext());
+ assertEquals(channelId, cursor.getLong(0));
+ assertNull(cursor.getString(1));
+ assertEquals(0, cursor.getInt(1));
}
}
@@ -309,9 +335,9 @@
verifyChannel(channelUri, values, channelId);
// Test: update
- values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, "1-1");
- values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, "One dash one");
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+ values.put(Channels.COLUMN_DISPLAY_NUMBER, "1-1");
+ values.put(Channels.COLUMN_DISPLAY_NAME, "One dash one");
+ values.put(Channels.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
mContentResolver.update(channelUri, values, null, null);
verifyChannel(channelUri, values, channelId);
@@ -324,64 +350,150 @@
}
}
+ public void testChannelsTableForIllegalAccess() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ ContentValues values = createDummyChannelValues(mInputId, false);
+ Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+
+ final String COLUMN_BROWSABLE = "browsable";
+ final String COLUMN_SYSTEM_APPROVED = "system_approved";
+
+ // Test: insert
+ values.put(COLUMN_BROWSABLE, 1);
+ values.putNull(COLUMN_SYSTEM_APPROVED);
+ try {
+ mContentResolver.insert(mChannelsUri, values);
+ fail("'" + COLUMN_BROWSABLE + "' should not be accessible.");
+ } catch (Exception e) {
+ // Expected.
+ }
+ values.putNull(COLUMN_BROWSABLE);
+ values.put(COLUMN_SYSTEM_APPROVED, 1);
+ try {
+ mContentResolver.insert(mChannelsUri, values);
+ fail("'" + COLUMN_SYSTEM_APPROVED + "' should not be accessible.");
+ } catch (Exception e) {
+ // Expected.
+ }
+
+ // Test: update
+ values.put(COLUMN_BROWSABLE, 1);
+ values.putNull(COLUMN_SYSTEM_APPROVED);
+ try {
+ mContentResolver.update(channelUri, values, null, null);
+ fail("'" + COLUMN_BROWSABLE + "' should not be accessible.");
+ } catch (Exception e) {
+ // Expected.
+ }
+ values.putNull(COLUMN_BROWSABLE);
+ values.put(COLUMN_SYSTEM_APPROVED, 1);
+ try {
+ mContentResolver.update(channelUri, values, null, null);
+ fail("'" + COLUMN_SYSTEM_APPROVED + "' should not be accessible.");
+ } catch (Exception e) {
+ // Expected.
+ }
+
+ mContentResolver.delete(mChannelsUri, null, null);
+ try (Cursor cursor = mContentResolver.query(
+ mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
+ assertEquals(0, cursor.getCount());
+ }
+ }
+
+ public void testChannelsTableForNonExistingColumns() throws Exception {
+ if (!Utils.hasTvInputFramework(getContext())) {
+ return;
+ }
+ ContentValues values = createDummyChannelValues(mInputId, false);
+ values.put(NON_EXISTING_COLUMN_NAME, "dummy value");
+ Uri rowUri = mContentResolver.insert(mChannelsUri, values);
+ long channelId = ContentUris.parseId(rowUri);
+ Uri channelUri = TvContract.buildChannelUri(channelId);
+ verifyChannel(channelUri, values, channelId);
+ verifyNonExistingColumn(channelUri, channelId);
+
+ // Test: update
+ mContentResolver.update(channelUri, values, null, null);
+ verifyChannel(channelUri, values, channelId);
+ verifyNonExistingColumn(channelUri, channelId);
+
+ // Test: delete
+ mContentResolver.delete(mChannelsUri, null, null);
+ try (Cursor cursor = mContentResolver.query(
+ mChannelsUri, CHANNELS_PROJECTION, null, null, null)) {
+ assertEquals(0, cursor.getCount());
+ }
+ }
+
private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
try (Cursor cursor = mContentResolver.query(
programUri, null, null, null, null)) {
assertNotNull(cursor);
assertEquals(cursor.getCount(), 1);
assertTrue(cursor.moveToNext());
- assertEquals(programId, cursor.getLong(cursor.getColumnIndex(TvContract.Programs._ID)));
- verifyLongColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CHANNEL_ID);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_TITLE);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_SEASON_TITLE);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_EPISODE_TITLE);
- verifyLongColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS);
- verifyLongColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_BROADCAST_GENRE);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CANONICAL_GENRE);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_LONG_DESCRIPTION);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VIDEO_WIDTH);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VIDEO_HEIGHT);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_AUDIO_LANGUAGE);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_CONTENT_RATING);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_POSTER_ART_URI);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_THUMBNAIL_URI);
- verifyBlobColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_VERSION_NUMBER);
+ assertEquals(programId, cursor.getLong(cursor.getColumnIndex(Programs._ID)));
+ verifyLongColumn(cursor, expectedValues, Programs.COLUMN_CHANNEL_ID);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_TITLE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SEASON_TITLE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_TITLE);
+ verifyLongColumn(cursor, expectedValues, Programs.COLUMN_START_TIME_UTC_MILLIS);
+ verifyLongColumn(cursor, expectedValues, Programs.COLUMN_END_TIME_UTC_MILLIS);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_BROADCAST_GENRE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CANONICAL_GENRE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_SHORT_DESCRIPTION);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_LONG_DESCRIPTION);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_WIDTH);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VIDEO_HEIGHT);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AUDIO_LANGUAGE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_CONTENT_RATING);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_POSTER_ART_URI);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_THUMBNAIL_URI);
+ verifyBlobColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_DATA);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VERSION_NUMBER);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_ID);
- verifyStringColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_VIDEO_URI);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_ID);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_PREVIEW_VIDEO_URI);
verifyIntegerColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_PREVIEW_LAST_PLAYBACK_POSITION);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_DURATION);
- verifyStringColumn(cursor, expectedValues,
- TvContract.Programs.COLUMN_PREVIEW_INTENT_URI);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_PREVIEW_WEIGHT);
+ Programs.COLUMN_LAST_PLAYBACK_POSITION_MILLIS);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_DURATION_MILLIS);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_APP_LINK_INTENT_URI);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_WEIGHT);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_TYPE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_WATCH_NEXT_TYPE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_POSTER_ART_ASPECT_RATIO);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_THUMBNAIL_ASPECT_RATIO);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_LOGO_URI);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AVAILABILITY);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_STARTING_PRICE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_OFFER_PRICE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_RELEASE_DATE);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_ITEM_COUNT);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_LIVE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_INTERACTION_TYPE);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_INTERACTION_COUNT);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_AUTHOR);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING_STYLE);
+ verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING);
}
}
private void verifyDeprecatedColumsInProgram(Uri programUri, ContentValues expectedValues) {
final String[] DEPRECATED_COLUMNS_PROJECTION = {
- TvContract.Programs.COLUMN_SEASON_NUMBER,
- TvContract.Programs.COLUMN_EPISODE_NUMBER,
+ Programs.COLUMN_SEASON_NUMBER,
+ Programs.COLUMN_EPISODE_NUMBER,
};
try (Cursor cursor = mContentResolver.query(
programUri, DEPRECATED_COLUMNS_PROJECTION, null, null, null)) {
assertNotNull(cursor);
assertEquals(cursor.getCount(), 1);
assertTrue(cursor.moveToNext());
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_SEASON_NUMBER);
- verifyIntegerColumn(cursor, expectedValues, TvContract.Programs.COLUMN_EPISODE_NUMBER);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_SEASON_NUMBER);
+ verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_EPISODE_NUMBER);
}
}
@@ -434,9 +546,9 @@
verifyProgram(programUri, values, programId);
// Test: update
- values.put(TvContract.Programs.COLUMN_TITLE, "new_program_title");
- values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "");
- values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+ values.put(Programs.COLUMN_TITLE, "new_program_title");
+ values.put(Programs.COLUMN_SHORT_DESCRIPTION, "");
+ values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
mContentResolver.update(programUri, values, null, null);
verifyProgram(programUri, values, programId);
@@ -461,9 +573,9 @@
verifyProgram(programUri, values, programId);
// Test: update
- values.put(TvContract.Programs.COLUMN_EPISODE_TITLE, "Sample title");
- values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, "Short description");
- values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+ values.put(Programs.COLUMN_EPISODE_TITLE, "Sample title");
+ values.put(Programs.COLUMN_SHORT_DESCRIPTION, "Short description");
+ values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
mContentResolver.update(programUri, values, null, null);
verifyProgram(programUri, values, programId);
@@ -482,14 +594,14 @@
}
// Test: insert
ContentValues expected = createDummyProgramValues(channelId);
- expected.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, "3");
- expected.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "9");
+ expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "3");
+ expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "9");
ContentValues input = new ContentValues(expected);
- input.remove(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER);
- input.remove(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
- input.put(TvContract.Programs.COLUMN_SEASON_NUMBER, 3);
- input.put(TvContract.Programs.COLUMN_EPISODE_NUMBER, 9);
+ input.remove(Programs.COLUMN_SEASON_DISPLAY_NUMBER);
+ input.remove(Programs.COLUMN_EPISODE_DISPLAY_NUMBER);
+ input.put(Programs.COLUMN_SEASON_NUMBER, 3);
+ input.put(Programs.COLUMN_EPISODE_NUMBER, 9);
Uri rowUri = mContentResolver.insert(programsUri, input);
long programId = ContentUris.parseId(rowUri);
@@ -498,10 +610,10 @@
verifyDeprecatedColumsInProgram(programUri, input);
// Test: update
- expected.put(TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER, "4");
- expected.put(TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "10");
- input.put(TvContract.Programs.COLUMN_SEASON_NUMBER, 4);
- input.put(TvContract.Programs.COLUMN_EPISODE_NUMBER, 10);
+ expected.put(Programs.COLUMN_SEASON_DISPLAY_NUMBER, "4");
+ expected.put(Programs.COLUMN_EPISODE_DISPLAY_NUMBER, "10");
+ input.put(Programs.COLUMN_SEASON_NUMBER, 4);
+ input.put(Programs.COLUMN_EPISODE_NUMBER, 10);
mContentResolver.update(programUri, input, null, null);
verifyProgram(programUri, expected, programId);
@@ -571,8 +683,8 @@
long channelId = ContentUris.parseId(channelUri);
Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
values = createDummyProgramValues(channelId);
- values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
- values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
+ values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
+ values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
mContentResolver.insert(programsUri, values);
// Overlap 1: starts early, ends early.
@@ -656,9 +768,9 @@
verifyRecordedProgram(recordedProgramUri, values, recordedProgramId);
// Test: update
- values.put(TvContract.RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title1");
- values.put(TvContract.RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
- values.put(TvContract.RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
+ values.put(RecordedPrograms.COLUMN_EPISODE_TITLE, "episode_title1");
+ values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
+ values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
"internal_provider_data1".getBytes());
mContentResolver.update(recordedProgramUri, values, null, null);
@@ -680,7 +792,7 @@
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
long channelId = ContentUris.parseId(channelUri);
- verifyRecordedProgramsTable(TvContract.RecordedPrograms.CONTENT_URI, channelId);
+ verifyRecordedProgramsTable(RecordedPrograms.CONTENT_URI, channelId);
}
private void verifyQueryWithSortOrder(Uri uri, final String[] projection,
@@ -725,68 +837,60 @@
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- final String[] projection = { TvContract.Channels._ID };
- verifyQueryWithSortOrder(TvContract.Channels.CONTENT_URI, projection,
- TvContract.Channels._ID + " ASC");
+ final String[] projection = { Channels._ID };
+ verifyQueryWithSortOrder(Channels.CONTENT_URI, projection, Channels._ID + " ASC");
}
public void testAllEpgPermissionBlocksSelectionOnQuery_Channels() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- final String[] projection = { TvContract.Channels._ID };
- verifyQueryWithSelection(TvContract.Channels.CONTENT_URI, projection,
- TvContract.Channels._ID + ">0");
+ final String[] projection = { Channels._ID };
+ verifyQueryWithSelection(Channels.CONTENT_URI, projection, Channels._ID + ">0");
}
public void testAllEpgPermissionBlocksSelectionOnUpdate_Channels() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- verifyUpdateWithSelection(TvContract.Channels.CONTENT_URI,
- TvContract.Channels._ID + ">0");
+ verifyUpdateWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
}
public void testAllEpgPermissionBlocksSelectionOnDelete_Channels() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- verifyDeleteWithSelection(TvContract.Channels.CONTENT_URI,
- TvContract.Channels._ID + ">0");
+ verifyDeleteWithSelection(Channels.CONTENT_URI, Channels._ID + ">0");
}
public void testAllEpgPermissionBlocksSortOrderOnQuery_Programs() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- final String[] projection = { TvContract.Programs._ID };
- verifyQueryWithSortOrder(TvContract.Programs.CONTENT_URI, projection,
- TvContract.Programs._ID + " ASC");
+ final String[] projection = { Programs._ID };
+ verifyQueryWithSortOrder(Programs.CONTENT_URI, projection, Programs._ID + " ASC");
}
public void testAllEpgPermissionBlocksSelectionOnQuery_Programs() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- final String[] projection = { TvContract.Channels._ID };
- verifyQueryWithSelection(TvContract.Programs.CONTENT_URI, projection,
- TvContract.Programs._ID + ">0");
+ final String[] projection = { Channels._ID };
+ verifyQueryWithSelection(Programs.CONTENT_URI, projection, Programs._ID + ">0");
}
public void testAllEpgPermissionBlocksSelectionOnUpdate_Programs() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- verifyUpdateWithSelection(TvContract.Programs.CONTENT_URI,
- TvContract.Programs._ID + ">0");
+ verifyUpdateWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
}
public void testAllEpgPermissionBlocksSelectionOnDelete_Programs() throws Exception {
if (!Utils.hasTvInputFramework(getContext())) {
return;
}
- verifyDeleteWithSelection(TvContract.Programs.CONTENT_URI,
- TvContract.Programs._ID + ">0");
+ verifyDeleteWithSelection(Programs.CONTENT_URI, Programs._ID + ">0");
}
public void testDefaultValues() throws Exception {
@@ -794,17 +898,16 @@
return;
}
ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
+ values.put(Channels.COLUMN_INPUT_ID, mInputId);
Uri channelUri = mContentResolver.insert(mChannelsUri, values);
assertNotNull(channelUri);
try (Cursor cursor = mContentResolver.query(
channelUri, CHANNELS_PROJECTION, null, null, null)) {
cursor.moveToNext();
- assertEquals(TvContract.Channels.TYPE_OTHER,
- cursor.getString(cursor.getColumnIndex(TvContract.Channels.COLUMN_TYPE)));
- assertEquals(TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO,
- cursor.getString(cursor.getColumnIndex(
- TvContract.Channels.COLUMN_SERVICE_TYPE)));
+ assertEquals(Channels.TYPE_OTHER,
+ cursor.getString(cursor.getColumnIndex(Channels.COLUMN_TYPE)));
+ assertEquals(Channels.SERVICE_TYPE_AUDIO_VIDEO,
+ cursor.getString(cursor.getColumnIndex(Channels.COLUMN_SERVICE_TYPE)));
}
values.clear();
}
@@ -971,11 +1074,11 @@
long channelId = ContentUris.parseId(channelUri);
long curTime = System.currentTimeMillis();
values = new ContentValues();
- values.put(TvContract.Programs.COLUMN_CHANNEL_ID, channelId);
- values.put(TvContract.Programs.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
- values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, curTime - 60000);
- values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, curTime + 60000);
- Uri programUri = mContentResolver.insert(TvContract.Programs.CONTENT_URI, values);
+ values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+ values.put(Programs.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+ values.put(Programs.COLUMN_START_TIME_UTC_MILLIS, curTime - 60000);
+ values.put(Programs.COLUMN_END_TIME_UTC_MILLIS, curTime + 60000);
+ Uri programUri = mContentResolver.insert(Programs.CONTENT_URI, values);
assertNotNull(programUri);
return programUri;
}
@@ -996,8 +1099,8 @@
}
String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
insertProgramWithBroadcastGenre(broadcastGenre);
- try (Cursor c = mContentResolver.query(TvContract.Programs.CONTENT_URI,
- new String[] {TvContract.Programs.COLUMN_BROADCAST_GENRE}, null, null, null)) {
+ try (Cursor c = mContentResolver.query(Programs.CONTENT_URI,
+ new String[] {Programs.COLUMN_BROADCAST_GENRE}, null, null, null)) {
assertNotNull(c);
assertEquals(1, c.getCount());
c.moveToNext();
diff --git a/tests/tests/uirendering/assets/linear-rgba16f.png b/tests/tests/uirendering/assets/linear-rgba16f.png
new file mode 100644
index 0000000..bad6a65
--- /dev/null
+++ b/tests/tests/uirendering/assets/linear-rgba16f.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/prophoto-rgba16f.png b/tests/tests/uirendering/assets/prophoto-rgba16f.png
new file mode 100644
index 0000000..5f855f2
--- /dev/null
+++ b/tests/tests/uirendering/assets/prophoto-rgba16f.png
Binary files differ
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
index a954294..77ff8f2 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
@@ -123,7 +123,8 @@
canvas.setDrawFilter(null);
};
createTest()
- .addCanvasClient(canvasClient)
+ // Picture does not support PaintFlagsDrawFilter
+ .addCanvasClientWithoutUsingPicture(canvasClient)
.runWithVerifier(getVerifierForTest(filterEnum, scaleUp));
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index a7587ba..444af9f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -202,13 +202,17 @@
new Rect(10, 10, 80, 80));
createTest()
- .addCanvasClient((canvas, width, height) -> {
+ // The border of the square is somehow blurred in HWUI OpenGL hardware mode with
+ // picture recording/playback. Maybe this is related to bug:31456967
+ // Hence disable picture mode for now.
+ .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
p.setColor(Color.BLUE);
canvas.drawRect(10, 10, 80, 80, p);
})
- .addCanvasClient((canvas, width, height) -> ninePatchDrawable.draw(canvas))
+ .addCanvasClientWithoutUsingPicture(
+ (canvas, width, height) -> ninePatchDrawable.draw(canvas))
.addLayout(R.layout.blue_padded_square, null)
.runWithVerifier(verifier);
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
index 37713de..e808296 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
@@ -54,6 +54,6 @@
// This means the source color (0x00ffffff) was
// properly pre-multiplied
0xffff0000
- }, 10)); // Tolerance set to account for dithering and interpolation
+ }, 20)); // Tolerance set to account for dithering and interpolation
}
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
index 988480e..03a040f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -65,7 +65,7 @@
@Test
public void testDecodeResource() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -77,7 +77,7 @@
public void testBitmapRegionDecode() throws IOException {
InputStream inputStream = mRes.openRawResource(R.drawable.robot);
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(10, 15, 34, 39),
HARDWARE_OPTIONS);
canvas.drawBitmap(hardwareBitmap, 0, 0, new Paint());
@@ -119,11 +119,11 @@
@Test
public void testSetDensity() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot);
bitmap.setDensity(DisplayMetrics.DENSITY_LOW);
canvas.drawBitmap(bitmap, 0, 0, null);
- }, true).addCanvasClient((canvas, width, height) -> {
+ }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
hardwareBitmap.setDensity(DisplayMetrics.DENSITY_LOW);
@@ -133,7 +133,7 @@
@Test
public void testNinePatch() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
InputStream is = mRes.openRawResource(R.drawable.blue_padded_square);
NinePatchDrawable ninePatch = (NinePatchDrawable) Drawable.createFromResourceStream(
mRes, null, is, null, HARDWARE_OPTIONS);
@@ -153,7 +153,7 @@
@Test
public void testCreateScaledBitmap() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
Bitmap scaled = Bitmap.createScaledBitmap(hardwareBitmap, 24, 24, false);
@@ -165,7 +165,7 @@
@Test
public void testCreateSubsetBitmap() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
Matrix matrix = new Matrix();
@@ -179,7 +179,7 @@
@Test
public void testCreateTransformedBitmap() {
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
Matrix matrix = new Matrix();
@@ -198,10 +198,10 @@
Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(), id, options);
assertEquals(from, bitmap.getConfig());
- createTest().addCanvasClient((canvas, width, height) -> {
+ createTest().addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
canvas.drawColor(Color.CYAN);
canvas.drawBitmap(bitmap, 0, 0, null);
- }, true).addCanvasClient((canvas, width, height) -> {
+ }, true).addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
canvas.drawColor(Color.CYAN);
Bitmap copy = bitmap.copy(to, false);
assertNotNull(copy);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
index 6f86ee7..f952bc23 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
@@ -63,7 +63,11 @@
}
};
createTest()
- .addCanvasClient(canvasClient)
+ // Because of the inverseComparer, we can't use Picture because
+ // software w/ picture = software w/o picture (same for hardware).
+ // (The inverseComparer assumes that there are only two render paths are they
+ // are different.)
+ .addCanvasClientWithoutUsingPicture(canvasClient)
.runWithComparer(inverseComparer);
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 2e7d88f..4fc8bc8 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -375,8 +375,8 @@
@Test
public void testSaveLayerClippedWithAlpha() {
// verify that renderer can draw nested clipped layers with different alpha
- createTest()
- .addCanvasClient((canvas, width, height) -> {
+ createTest() // picture mode is disable due to bug:34871089
+ .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Paint redPaint = new Paint();
redPaint.setColor(0xffff0000);
canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, Canvas.CLIP_TO_LAYER_SAVE_FLAG);
@@ -391,8 +391,8 @@
@Test
public void testSaveLayerUnclippedWithAlpha() {
// verify that renderer can draw nested unclipped layers with different alpha
- createTest()
- .addCanvasClient((canvas, width, height) -> {
+ createTest() // picture mode is disable due to bug:34871089
+ .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
Paint redPaint = new Paint();
redPaint.setColor(0xffff0000);
canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f, 0);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java
new file mode 100644
index 0000000..3faeb4a4
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Rgba16fTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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 android.uirendering.cts.testclasses;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Shader;
+import android.support.test.filters.MediumTest;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+@MediumTest
+public class Rgba16fTests extends ActivityTestBase {
+ @Test
+ public void testTransferFunctions() {
+ createTest()
+ .addCanvasClient("RGBA16F_TransferFunctions", (canvas, width, height) -> {
+ AssetManager assets = getActivity().getResources().getAssets();
+ try (InputStream in = assets.open("linear-rgba16f.png")) {
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+ canvas.scale(
+ width / (float) bitmap.getWidth(),
+ height / (float) bitmap.getHeight());
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ } catch (IOException e) {
+ throw new RuntimeException("Test failed: ", e);
+ }
+ }, true)
+ .runWithVerifier(new SamplePointVerifier(
+ new Point[] { new Point(0, 0) },
+ new int[] { 0xffbbbbbb }
+ ));
+ }
+
+ @Test
+ public void testTransferFunctionsShader() {
+ createTest()
+ .addCanvasClient("RGBA16F_TransferFunctions_Shader", (canvas, width, height) -> {
+ AssetManager assets = getActivity().getResources().getAssets();
+ try (InputStream in = assets.open("linear-rgba16f.png")) {
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+ Paint p = new Paint();
+ p.setShader(new BitmapShader(bitmap,
+ Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+ canvas.drawRect(0.0f, 0.0f, width, height, p);
+ } catch (IOException e) {
+ throw new RuntimeException("Test failed: ", e);
+ }
+ }, true)
+ .runWithVerifier(new SamplePointVerifier(
+ new Point[] { new Point(0, 0) },
+ new int[] { 0xffbbbbbb }
+ ));
+ }
+
+ @Test
+ public void testMirroredTransferFunctions() {
+ createTest()
+ .addCanvasClient("RGBA16F_TransferFunctions_Mirror", (canvas, width, height) -> {
+ AssetManager assets = getActivity().getResources().getAssets();
+ // Pure blue in ProPhoto RGB will yield negative R and G values in scRGB,
+ // as well as a value > 1.0 for B
+ try (InputStream in = assets.open("prophoto-rgba16f.png")) {
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+ canvas.scale(
+ width / (float) bitmap.getWidth(),
+ height / (float) bitmap.getHeight());
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ } catch (IOException e) {
+ throw new RuntimeException("Test failed: ", e);
+ }
+ }, true)
+ .runWithVerifier(new SamplePointVerifier(
+ new Point[] { new Point(0, 0) },
+ new int[] { 0xff0000ff }
+ ));
+ }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
index 856fc17..16df48a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
@@ -118,8 +118,9 @@
// Create the test cases with each combination
do {
int arrIndex = Math.min(index, bitmapComparers.length - 1);
- createTest()
- .addCanvasClient(modifierAccessor.getDebugString(), canvasClient)
+ createTest() // picture mode is disable due to bug:34871089
+ .addCanvasClientWithoutUsingPicture(
+ modifierAccessor.getDebugString(), canvasClient)
.runWithComparer(bitmapComparers[arrIndex]);
index++;
} while (modifierAccessor.step());
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index e340899..ca5ee18 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -138,7 +138,7 @@
protected Point runRenderSpec(TestCase testCase) {
Point testOffset = getActivity().enqueueRenderSpecAndWait(
testCase.layoutID, testCase.canvasClient,
- testCase.viewInitializer, testCase.useHardware);
+ testCase.viewInitializer, testCase.useHardware, testCase.usePicture);
testCase.wasTestRan = true;
if (testCase.readyFence != null) {
try {
@@ -298,7 +298,28 @@
public TestCaseBuilder addCanvasClient(String debugString,
CanvasClient canvasClient, boolean useHardware) {
- mTestCases.add(new TestCase(canvasClient, debugString, useHardware));
+ return addCanvasClientInternal(debugString, canvasClient, useHardware, false)
+ .addCanvasClientInternal(debugString, canvasClient, useHardware, true);
+ }
+
+ public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient) {
+ return addCanvasClientWithoutUsingPicture(null, canvasClient);
+ }
+
+ public TestCaseBuilder addCanvasClientWithoutUsingPicture(String debugString,
+ CanvasClient canvasClient) {
+ return addCanvasClientInternal(debugString, canvasClient, false, false)
+ .addCanvasClientInternal(debugString, canvasClient, true, false);
+ }
+
+ public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient,
+ boolean useHardware) {
+ return addCanvasClientInternal(null, canvasClient, useHardware, false);
+ }
+
+ private TestCaseBuilder addCanvasClientInternal(String debugString,
+ CanvasClient canvasClient, boolean useHardware, boolean usePicture) {
+ mTestCases.add(new TestCase(canvasClient, debugString, useHardware, usePicture));
return this;
}
@@ -320,6 +341,7 @@
public String canvasClientDebugString;
public boolean useHardware;
+ public boolean usePicture = false;
public boolean wasTestRan = false;
public TestCase(int layoutId, ViewInitializer viewInitializer, boolean useHardware) {
@@ -328,10 +350,12 @@
this.useHardware = useHardware;
}
- public TestCase(CanvasClient client, String debugString, boolean useHardware) {
+ public TestCase(CanvasClient client, String debugString, boolean useHardware,
+ boolean usePicture) {
this.canvasClient = client;
this.canvasClientDebugString = debugString;
this.useHardware = useHardware;
+ this.usePicture = usePicture;
}
public String getDebugString() {
@@ -347,7 +371,8 @@
debug += "Layout resource : " +
getActivity().getResources().getResourceName(layoutID);
}
- debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + "\n";
+ debug += "\nTest ran in " + (useHardware ? "hardware" : "software") +
+ (usePicture ? " with picture" : " without picture") + "\n";
return debug;
}
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
index 81183e5..8012c82 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
@@ -19,6 +19,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Picture;
import android.util.AttributeSet;
import android.view.View;
@@ -26,6 +27,7 @@
* A simple View that uses a CanvasClient to draw its contents
*/
public class CanvasClientView extends View {
+ private boolean mUsePicture = false;
private CanvasClient mCanvasClient;
public CanvasClientView(Context context) {
@@ -40,6 +42,10 @@
super(context, attrs, defStyleAttr);
}
+ public void setUsePicture(boolean usePicture) {
+ mUsePicture = usePicture;
+ }
+
public void setCanvasClient(CanvasClient canvasClient) {
mCanvasClient = canvasClient;
}
@@ -57,7 +63,17 @@
int saveCount = canvas.save();
canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
- mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+ if (mUsePicture) {
+ Picture picture = new Picture();
+ Canvas pictureCanvas = picture.beginRecording(ActivityTestBase.TEST_WIDTH,
+ ActivityTestBase.TEST_HEIGHT);
+ mCanvasClient.draw(pictureCanvas, ActivityTestBase.TEST_WIDTH,
+ ActivityTestBase.TEST_HEIGHT);
+ picture.endRecording();
+ picture.draw(canvas);
+ } else {
+ mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+ }
canvas.restoreToCount(saveCount);
}
}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 2c5a58e..13698c9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -54,11 +54,12 @@
}
public Point enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient,
- @Nullable ViewInitializer viewInitializer, boolean useHardware) {
+ @Nullable ViewInitializer viewInitializer, boolean useHardware, boolean usePicture) {
((RenderSpecHandler) mHandler).setViewInitializer(viewInitializer);
int arg2 = (useHardware ? View.LAYER_TYPE_NONE : View.LAYER_TYPE_SOFTWARE);
if (canvasClient != null) {
- mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, 0, arg2, canvasClient).sendToTarget();
+ mHandler.obtainMessage(RenderSpecHandler.CANVAS_MSG, usePicture ? 1 : 0,
+ arg2, canvasClient).sendToTarget();
} else {
mHandler.obtainMessage(RenderSpecHandler.LAYOUT_MSG, layoutId, arg2).sendToTarget();
}
@@ -126,6 +127,9 @@
stub.setLayoutResource(R.layout.test_content_canvasclientview);
mView = stub.inflate();
((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
+ if (message.arg1 != 0) {
+ ((CanvasClientView) mView).setUsePicture(true);
+ }
} break;
}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 0c6f302..25ebdca 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -161,6 +161,9 @@
android:resource="@xml/merge" />
</activity>
+ <activity android:name="android.view.cts.MenuTestActivity"
+ android:label="MenuTestActivity" />
+
<activity android:name="android.view.cts.ActionModeCtsActivity"
android:label="ActionModeCtsActivity">
</activity>
diff --git a/tests/tests/view/res/menu/shortcut_modifiers.xml b/tests/tests/view/res/menu/shortcut_modifiers.xml
new file mode 100644
index 0000000..4204940
--- /dev/null
+++ b/tests/tests/view/res/menu/shortcut_modifiers.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/no_modifiers"
+ android:onClick="handleMenuItem"
+ android:alphabeticShortcut="a" />
+
+ <item android:id="@+id/default_modifiers"
+ android:alphabeticShortcut="b"
+ android:onClick="handleMenuItem"
+ android:alphabeticModifiers="CTRL" />
+
+ <item android:id="@+id/single_modifier"
+ android:alphabeticShortcut="c"
+ android:onClick="handleMenuItem"
+ android:alphabeticModifiers="SHIFT" />
+
+ <item android:id="@+id/multiple_modifiers"
+ android:alphabeticShortcut="d"
+ android:onClick="handleMenuItem"
+ android:alphabeticModifiers="CTRL|SHIFT" />
+
+</menu>
diff --git a/tests/tests/view/res/menu/visible_shortcut.xml b/tests/tests/view/res/menu/visible_shortcut.xml
index 4d6f362..57ceda1 100644
--- a/tests/tests/view/res/menu/visible_shortcut.xml
+++ b/tests/tests/view/res/menu/visible_shortcut.xml
@@ -34,4 +34,19 @@
</group>
+ <item android:id="@+id/no_modifiers"
+ android:alphabeticShortcut="d" />
+
+ <item android:id="@+id/default_modifiers"
+ android:alphabeticShortcut="e"
+ android:alphabeticModifiers="CTRL" />
+
+ <item android:id="@+id/single_modifier"
+ android:alphabeticShortcut="f"
+ android:alphabeticModifiers="SHIFT" />
+
+ <item android:id="@+id/multiple_modifiers"
+ android:alphabeticShortcut="g"
+ android:alphabeticModifiers="CTRL|SHIFT" />
+
</menu>
diff --git a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
index bc9933f..2bb0278 100644
--- a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
+++ b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
@@ -31,6 +31,7 @@
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -112,6 +113,30 @@
@UiThreadTest
@Test
+ public void testInflateShortcutModifiersFromXml() {
+ mMenuInflater.inflate(R.menu.visible_shortcut, mMenu);
+ MenuItem mMenuItem;
+
+ mMenuItem = mMenu.findItem(R.id.no_modifiers);
+ assertEquals('d', mMenuItem.getAlphabeticShortcut());
+ assertEquals(KeyEvent.META_CTRL_ON, mMenuItem.getAlphabeticModifiers());
+
+ mMenuItem = mMenu.findItem(R.id.default_modifiers);
+ assertEquals('e', mMenuItem.getAlphabeticShortcut());
+ assertEquals(KeyEvent.META_CTRL_ON, mMenuItem.getAlphabeticModifiers());
+
+ mMenuItem = mMenu.findItem(R.id.single_modifier);
+ assertEquals('f', mMenuItem.getAlphabeticShortcut());
+ assertEquals(KeyEvent.META_SHIFT_ON, mMenuItem.getAlphabeticModifiers());
+
+ mMenuItem = mMenu.findItem(R.id.multiple_modifiers);
+ assertEquals('g', mMenuItem.getAlphabeticShortcut());
+ assertEquals(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON,
+ mMenuItem.getAlphabeticModifiers());
+ }
+
+ @UiThreadTest
+ @Test
public void testInflateDrawableFromXml() {
// the titles and icons
mMenuInflater.inflate(R.menu.title_icon, mMenu);
diff --git a/tests/tests/view/src/android/view/cts/MenuTest.java b/tests/tests/view/src/android/view/cts/MenuTest.java
new file mode 100644
index 0000000..d01cd8b
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2008 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 android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.widget.PopupMenu;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link MenuInflater}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MenuTest {
+ private MenuTestActivity mActivity;
+ private MenuInflater mMenuInflater;
+ private Menu mMenu;
+
+ @Rule
+ public ActivityTestRule<MenuTestActivity> mActivityRule =
+ new ActivityTestRule<>(MenuTestActivity.class);
+
+ @Before
+ public void setup() {
+ mActivity = (MenuTestActivity) mActivityRule.getActivity();
+ mMenuInflater = mActivity.getMenuInflater();
+ mMenu = new PopupMenu(mActivity, null).getMenu();
+ }
+
+ @UiThreadTest
+ @Test
+ public void testPerformShortcut() {
+ mMenuInflater.inflate(R.menu.shortcut_modifiers, mMenu);
+ mMenu.setQwertyMode(true);
+ final long downTime = SystemClock.uptimeMillis();
+ int keyCodeToSend, metaState;
+ KeyEvent keyEventToSend;
+
+ // Test shortcut trigger in case of no modifier
+ keyCodeToSend = KeyEvent.KEYCODE_A;
+ metaState = KeyEvent.META_CTRL_ON;
+ keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, metaState);
+ assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+ assertEquals(mActivity.getMenuItemIdTracker(),
+ mMenu.findItem(R.id.no_modifiers).getItemId());
+
+ // Test shortcut trigger in case of default modifier
+ keyCodeToSend = KeyEvent.KEYCODE_B;
+ metaState = KeyEvent.META_CTRL_ON;
+ keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, metaState);
+ assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+ assertEquals(mActivity.getMenuItemIdTracker(),
+ mMenu.findItem(R.id.default_modifiers).getItemId());
+
+ // Test shortcut trigger in case of non-default single modifier
+ keyCodeToSend = KeyEvent.KEYCODE_C;
+ metaState = KeyEvent.META_SHIFT_ON;
+ keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, metaState);
+ assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+ assertEquals(mActivity.getMenuItemIdTracker(),
+ mMenu.findItem(R.id.single_modifier).getItemId());
+
+ // Test shortcut trigger in case of multiple modifiers
+ keyCodeToSend = KeyEvent.KEYCODE_D;
+ metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON;
+ keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, metaState);
+ assertTrue(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+ assertEquals(mActivity.getMenuItemIdTracker(),
+ mMenu.findItem(R.id.multiple_modifiers).getItemId());
+
+ // Test no shortcut trigger in case of incorrect modifier
+ keyCodeToSend = KeyEvent.KEYCODE_E;
+ metaState = KeyEvent.META_CTRL_ON;
+ keyEventToSend = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, metaState);
+ assertFalse(mMenu.performShortcut(keyCodeToSend, keyEventToSend, 0));
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/MenuTestActivity.java b/tests/tests/view/src/android/view/cts/MenuTestActivity.java
new file mode 100644
index 0000000..d5d5c4a
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/MenuTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2008 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 android.view.cts;
+
+import android.app.Activity;
+import android.view.MenuItem;
+
+public class MenuTestActivity extends Activity {
+
+ private int mMenuItemIdTracker;
+
+ public int getMenuItemIdTracker() {
+ return mMenuItemIdTracker;
+ }
+
+ public boolean handleMenuItem(MenuItem item) {
+ mMenuItemIdTracker = item.getItemId();
+ return true;
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewGroupTest.java b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
index 5b8d17f..f051a27 100644
--- a/tests/tests/view/src/android/view/cts/ViewGroupTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewGroupTest.java
@@ -29,6 +29,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.XmlResourceParser;
@@ -45,6 +47,7 @@
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -69,11 +72,13 @@
import android.view.animation.RotateAnimation;
import android.view.animation.Transformation;
import android.view.cts.util.XmlUtils;
+import android.widget.Button;
import android.widget.TextView;
import com.android.compatibility.common.util.CTSResult;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -91,6 +96,10 @@
private TextView mTextView;
private MockTextView mMockTextView;
+ @Rule
+ public ActivityTestRule<CtsActivity> mCtsActivityRule =
+ new ActivityTestRule<>(CtsActivity.class, false, false);
+
private final Sync mSync = new Sync();
private static class Sync {
boolean mHasNotify;
@@ -973,6 +982,43 @@
assertEquals(CTSResult.RESULT_OK, mResultCode);
}
+ @Test
+ public void testOnDescendantInvalidated() throws Throwable {
+ Activity activity = null;
+ try {
+ activity = mCtsActivityRule.launchActivity(new Intent());
+
+ mCtsActivityRule.runOnUiThread(() -> {
+ View child = mTextView;
+ MockViewGroup parent = mMockViewGroup;
+ MockViewGroup grandParent = new MockViewGroup(mContext);
+ parent.addView(child);
+ grandParent.addView(parent);
+ mCtsActivityRule.getActivity().setContentView(grandParent);
+
+ parent.isOnDescendantInvalidatedCalled = false;
+ grandParent.isOnDescendantInvalidatedCalled = false;
+
+ parent.invalidateChild(child, new Rect(0, 0, 1, 1));
+
+ assertTrue(parent.isOnDescendantInvalidatedCalled);
+ assertTrue(grandParent.isOnDescendantInvalidatedCalled);
+
+ parent.isOnDescendantInvalidatedCalled = false;
+ grandParent.isOnDescendantInvalidatedCalled = false;
+
+ grandParent.invalidateChild(child, new Rect(0, 0, 1, 1));
+
+ assertFalse(parent.isOnDescendantInvalidatedCalled);
+ assertTrue(grandParent.isOnDescendantInvalidatedCalled);
+ });
+ } finally {
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+ }
+
private void waitForResult() {
synchronized (mSync) {
while(!mSync.mHasNotify) {
@@ -1444,78 +1490,175 @@
assertTrue(mMockViewGroup.isOnRequestFocusInDescendantsCalled);
}
- private void setupRestoreDefaultFocus() {
- // Mark 2 children as focusable and add to the parent, then mark the second one as focused
- // by default.
- mMockViewGroup = new MockViewGroup(mContext);
- mMockTextView = new MockTextView(mContext);
- mMockTextView.setFocusable(true);
- mTextView = new TextView(mContext);
- mTextView.setFocusable(true);
- mMockViewGroup.addView(mMockTextView);
- mMockViewGroup.addView(mTextView);
- mTextView.setFocusedByDefault(true);
+ private class TestClusterHier {
+ public MockViewGroup top = new MockViewGroup(mContext);
+ public MockViewGroup cluster1 = new MockViewGroup(mContext);
+ public Button c1view1 = new Button(mContext);
+ public Button c1view2 = new Button(mContext);
+ public MockViewGroup cluster2 = new MockViewGroup(mContext);
+ public MockViewGroup nestedGroup = new MockViewGroup(mContext);
+ public Button c2view1 = new Button(mContext);
+ public Button c2view2 = new Button(mContext);
+ TestClusterHier() {
+ for (Button bt : new Button[]{c1view1, c1view2, c2view1, c2view2}) {
+ // Otherwise this test won't work during suite-run.
+ bt.setFocusableInTouchMode(true);
+ }
+ cluster1.setKeyboardNavigationCluster(true);
+ cluster2.setKeyboardNavigationCluster(true);
+ cluster1.addView(c1view1);
+ cluster1.addView(c1view2);
+ cluster2.addView(c2view1);
+ nestedGroup.addView(c2view2);
+ cluster2.addView(nestedGroup);
+ top.addView(cluster1);
+ top.addView(cluster2);
+ }
+ }
+
+ @UiThreadTest
+ @Test
+ public void testRestoreFocusInCluster() {
+ TestClusterHier h = new TestClusterHier();
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view1, h.top.findFocus());
+
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c2view1, h.top.findFocus());
+
+ h.c2view2.setFocusedInCluster();
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c2view2, h.top.findFocus());
+ h.c2view1.setFocusedInCluster();
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c2view1, h.top.findFocus());
+
+ h.c1view2.setFocusedInCluster();
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view2, h.top.findFocus());
+
+ h = new TestClusterHier();
+ h.cluster1.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertNull(h.top.findFocus());
+
+ h.c2view1.setVisibility(View.INVISIBLE);
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c2view2, h.top.findFocus());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFocusInClusterRemovals() {
+ // Removing focused-in-cluster view from its parent in various ways.
+ TestClusterHier h = new TestClusterHier();
+ h.c1view1.setFocusedInCluster();
+ h.cluster1.removeView(h.c1view1);
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view2, h.cluster1.findFocus());
+
+ h = new TestClusterHier();
+ h.c1view1.setFocusedInCluster();
+ h.cluster1.removeViews(0, 1);
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view2, h.cluster1.findFocus());
+
+ h = new TestClusterHier();
+ h.c2view1.setFocusedInCluster();
+ h.cluster2.removeAllViewsInLayout();
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertNull(h.cluster2.findFocus());
+
+ h = new TestClusterHier();
+ h.c1view1.setFocusedInCluster();
+ h.cluster1.detachViewFromParent(h.c1view1);
+ h.cluster1.attachViewToParent(h.c1view1, 1, null);
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view1, h.cluster1.findFocus());
+
+ h = new TestClusterHier();
+ h.c1view1.setFocusedInCluster();
+ h.cluster1.detachViewFromParent(h.c1view1);
+ h.cluster1.removeDetachedView(h.c1view1, false);
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view2, h.cluster1.findFocus());
}
@UiThreadTest
@Test
public void testRestoreDefaultFocus() {
- // Invoking restoreDefaultFocus with various conditions that affect the outcome.
- setupRestoreDefaultFocus();
- mTextView.setFocusedByDefault(false);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
+ TestClusterHier h = new TestClusterHier();
+ h.c1view2.setFocusedByDefault(true);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view2, h.top.findFocus());
- setupRestoreDefaultFocus();
- mTextView.setKeyboardNavigationCluster(true);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
+ h.c1view2.setFocusedByDefault(false);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view1, h.top.findFocus());
- setupRestoreDefaultFocus();
- mMockViewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(null, mMockViewGroup.findFocus());
+ // default focus favors higher-up views
+ h.c1view2.setFocusedByDefault(true);
+ h.cluster1.setFocusedByDefault(true);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view2, h.top.findFocus());
+ h.c2view1.setFocusedByDefault(true);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view2, h.top.findFocus());
+ h.cluster2.setFocusedByDefault(true);
+ h.cluster1.setFocusedByDefault(false);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c2view1, h.top.findFocus());
- setupRestoreDefaultFocus();
- mTextView.setVisibility(View.INVISIBLE);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
-
- setupRestoreDefaultFocus();
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mTextView, mMockViewGroup.findFocus());
+ // removing default receivers should resolve to an existing default
+ h = new TestClusterHier();
+ h.c1view2.setFocusedByDefault(true);
+ h.cluster1.setFocusedByDefault(true);
+ h.c2view2.setFocusedByDefault(true);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view2, h.top.findFocus());
+ h.c1view2.setFocusedByDefault(false);
+ h.cluster1.setFocusedByDefault(false);
+ // only 1 focused-by-default view left, but its in a different branch. Should still pull
+ // default focus.
+ h.top.restoreDefaultFocus();
+ assertSame(h.c2view2, h.top.findFocus());
}
@UiThreadTest
@Test
public void testDefaultFocusViewRemoved() {
// Removing default-focus view from its parent in various ways.
- setupRestoreDefaultFocus();
- mMockViewGroup.removeView(mTextView);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
+ TestClusterHier h = new TestClusterHier();
+ h.c1view1.setFocusedByDefault(true);
+ h.cluster1.removeView(h.c1view1);
+ h.cluster1.restoreDefaultFocus();
+ assertSame(h.c1view2, h.cluster1.findFocus());
- setupRestoreDefaultFocus();
- mMockViewGroup.removeViews(1, 1);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
+ h = new TestClusterHier();
+ h.c1view1.setFocusedByDefault(true);
+ h.cluster1.removeViews(0, 1);
+ h.cluster1.restoreDefaultFocus();
+ assertSame(h.c1view2, h.cluster1.findFocus());
- setupRestoreDefaultFocus();
- mMockViewGroup.removeAllViewsInLayout();
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(null, mMockViewGroup.findFocus());
+ h = new TestClusterHier();
+ h.c1view1.setFocusedByDefault(true);
+ h.cluster1.removeAllViewsInLayout();
+ h.cluster1.restoreDefaultFocus();
+ assertNull(h.cluster1.findFocus());
- setupRestoreDefaultFocus();
- mMockViewGroup.detachViewFromParent(mTextView);
- mMockViewGroup.attachViewToParent(mTextView, 1, null);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mTextView, mMockViewGroup.findFocus());
+ h = new TestClusterHier();
+ h.c1view1.setFocusedByDefault(true);
+ h.cluster1.detachViewFromParent(h.c1view1);
+ h.cluster1.attachViewToParent(h.c1view1, 1, null);
+ h.cluster1.restoreDefaultFocus();
+ assertSame(h.c1view1, h.cluster1.findFocus());
- setupRestoreDefaultFocus();
- mMockViewGroup.detachViewFromParent(mTextView);
- mMockViewGroup.removeDetachedView(mTextView, false);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
- assertSame(mMockTextView, mMockViewGroup.findFocus());
+ h = new TestClusterHier();
+ h.c1view1.setFocusedByDefault(true);
+ h.cluster1.detachViewFromParent(h.c1view1);
+ h.cluster1.removeDetachedView(h.c1view1, false);
+ h.cluster1.restoreDefaultFocus();
+ assertSame(h.c1view2, h.cluster1.findFocus());
}
@UiThreadTest
@@ -1527,15 +1670,34 @@
mMockTextView.setFocusable(true);
mTextView = new TextView(mContext);
mTextView.setFocusable(true);
+ mTextView.setFocusableInTouchMode(true);
mTextView.setFocusedByDefault(true);
mMockViewGroup.addView(mMockTextView);
mMockViewGroup.addView(mTextView);
- mMockViewGroup.restoreDefaultFocus(View.FOCUS_FORWARD);
+ mMockViewGroup.restoreDefaultFocus();
assertTrue(mTextView.isFocused());
}
@UiThreadTest
@Test
+ public void testDefaultFocusWorksForClusters() {
+ TestClusterHier h = new TestClusterHier();
+ h.c2view2.setFocusedByDefault(true);
+ h.cluster1.setFocusedByDefault(true);
+ h.top.restoreDefaultFocus();
+ assertSame(h.c1view1, h.top.findFocus());
+ h.cluster2.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c2view2, h.top.findFocus());
+
+ // make sure focused in cluster takes priority in cluster-focus
+ h.c1view2.setFocusedByDefault(true);
+ h.c1view1.setFocusedInCluster();
+ h.cluster1.restoreFocusInCluster(View.FOCUS_DOWN);
+ assertSame(h.c1view1, h.top.findFocus());
+ }
+
+ @UiThreadTest
+ @Test
public void testRequestTransparentRegion() {
MockViewGroup parent = new MockViewGroup(mContext);
MockView child1 = new MockView(mContext);
@@ -2401,6 +2563,7 @@
public boolean isDrawableStateChangedCalled;
public boolean isRequestLayoutCalled;
public boolean isOnLayoutCalled;
+ public boolean isOnDescendantInvalidatedCalled;
public int left;
public int top;
public int right;
@@ -2760,6 +2923,12 @@
public boolean isChildrenDrawnWithCacheEnabled() {
return super.isChildrenDrawnWithCacheEnabled();
}
+
+ @Override
+ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
+ isOnDescendantInvalidatedCalled = true;
+ super.onDescendantInvalidated(child, target);
+ }
}
static class MockView2 extends View {
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 472d660..e874aef 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -2872,7 +2872,7 @@
@Test
public void testRestoreDefaultFocus() {
MockView view = new MockView(mActivity);
- view.restoreDefaultFocus(0);
+ view.restoreDefaultFocus();
assertTrue(view.hasCalledRequestFocus());
}
diff --git a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
index dd83c16..53b24d8 100644
--- a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
+++ b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
@@ -31,6 +31,7 @@
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -254,4 +255,46 @@
auto.setFocusable(false);
assertSame(View.NOT_FOCUSABLE, auto.getFocusable());
}
+
+ @UiThreadTest
+ @Test
+ public void testHasFocusable() {
+ final Activity activity = mActivityRule.getActivity();
+ final ViewGroup group = (ViewGroup) activity.findViewById(R.id.auto_test_area);
+
+ View singleView = new View(activity);
+ group.addView(singleView);
+
+ testHasFocusable(singleView);
+
+ group.removeView(singleView);
+
+ View groupView = new FrameLayout(activity);
+ group.addView(groupView);
+
+ testHasFocusable(groupView);
+ }
+
+ private void testHasFocusable(View view) {
+ assertEquals("single view was not auto-focusable", View.FOCUSABLE_AUTO,
+ view.getFocusable());
+ assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+ assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+ view.setClickable(true);
+ assertTrue("single view doesn't hasFocusable", view.hasFocusable());
+ assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+ view.setClickable(false);
+ assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+ assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+ view.setFocusable(View.NOT_FOCUSABLE);
+ assertFalse("single view unexpectedly hasFocusable", view.hasFocusable());
+ assertFalse("single view unexpectedly hasExplicitFocusable", view.hasExplicitFocusable());
+
+ view.setFocusable(View.FOCUSABLE);
+ assertTrue("single view doesn't hasFocusable", view.hasFocusable());
+ assertTrue("single view doesn't hasExplicitFocusable", view.hasExplicitFocusable());
+ }
}
diff --git a/tests/tests/widget/res/font/samplefont.ttf b/tests/tests/widget/res/font/samplefont.ttf
new file mode 100644
index 0000000..49f1c62
--- /dev/null
+++ b/tests/tests/widget/res/font/samplefont.ttf
Binary files differ
diff --git a/tests/tests/widget/res/font/samplexmlfont.xml b/tests/tests/widget/res/font/samplexmlfont.xml
new file mode 100644
index 0000000..2905c13
--- /dev/null
+++ b/tests/tests/widget/res/font/samplexmlfont.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 1af5557..6e311d6 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -287,6 +287,63 @@
android:autoSizeMaxTextSize="50dp"
android:autoSizeStepGranularity="1dp" />
+ <TextView
+ android:id="@+id/textview_fontresource_fontfamily"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@font/samplefont" />
+
+ <TextView
+ android:id="@+id/textview_fontxmlresource_fontfamily"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@font/samplexmlfont" />
+
+ <TextView
+ android:id="@+id/textview_fontresource_style"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextView_FontResource" />
+
+ <TextView
+ android:id="@+id/textview_fontxmlresource_style"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextView_FontXmlResource" />
+
+ <TextView
+ android:id="@+id/textview_fontresource_textAppearance"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextView_FontResource" />
+
+ <TextView
+ android:id="@+id/textview_fontxmlresource_textAppearance"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextView_FontXmlResource" />
+
+ <TextView
+ android:id="@+id/textview_autosize_xy_predef_sizes"
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:text="@string/sample_text"
+ android:autoSizeText="xy"
+ android:autoSizeStepSizeSet="@array/auto_size_predefined_sizes" />
+
+ <TextView
+ android:id="@+id/textview_autosize_xy_predef_sizes_redundant_values"
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:text="@string/sample_text"
+ android:autoSizeText="xy"
+ android:autoSizeStepSizeSet="@array/auto_size_predefined_sizes_redundant_values" />
</LinearLayout>
</ScrollView>
diff --git a/tests/tests/widget/res/values/arrays.xml b/tests/tests/widget/res/values/arrays.xml
index 71e0133..45d3a1d 100644
--- a/tests/tests/widget/res/values/arrays.xml
+++ b/tests/tests/widget/res/values/arrays.xml
@@ -54,4 +54,21 @@
<item>9</item>
<item>10</item>
</integer-array>
+
+ <array name="auto_size_predefined_sizes">
+ <item>10px</item>
+ <item>10dip</item>
+ <item>10sp</item>
+ <item>10pt</item>
+ <item>10in</item>
+ <item>10mm</item>
+ </array>
+
+ <array name="auto_size_predefined_sizes_redundant_values">
+ <item>40px</item>
+ <item>10px</item>
+ <item>10px</item>
+ <item>10px</item>
+ <item>0dp</item>
+ </array>
</resources>
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index 7ba9711..a47f169 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -227,4 +227,13 @@
<!-- Force swipe-to-dismiss to false. -->
<item name="android:windowSwipeToDismiss">false</item>
</style>
+
+ <style name="TextView_FontResource">
+ <item name="android:fontFamily">@font/samplefont</item>
+ <item name="android:textAppearance">@null</item>
+ </style>
+
+ <style name="TextView_FontXmlResource">
+ <item name="android:fontFamily">@font/samplexmlfont</item>
+ </style>
</resources>
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 46755c0..d360ace 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -16,6 +16,7 @@
package android.widget.cts;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -156,6 +157,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Locale;
/**
@@ -3361,6 +3363,22 @@
"\"smcp\" on", mTextView.getFontFeatureSettings());
}
+ @UiThreadTest
+ @Test
+ public void testSetGetFontVariationSettings() {
+ mTextView = new TextView(mActivity);
+
+ // The default font variation settings should be null.
+ assertNull(mTextView.getFontVariationSettings());
+
+ final String setting = "'wdth' 2.0";
+ mTextView.setFontVariationSettings(setting);
+ assertEquals(setting, mTextView.getFontVariationSettings());
+
+ mTextView.setFontVariationSettings("");
+ assertNull(mTextView.getFontVariationSettings());
+ }
+
@Test
public void testGetOffsetForPositionSingleLineLtr() throws Throwable {
// asserts getOffsetPosition returns correct values for a single line LTR text
@@ -6224,7 +6242,6 @@
ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
CtsTouchUtils.emulateTapOnView(mInstrumentation, mTextView, spanDetails.mXPosInside,
spanDetails.mYPosInside);
- SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 1);
verify(spanDetails.mClickableSpan, times(1)).onClick(mTextView);
}
@@ -6233,7 +6250,7 @@
ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
CtsTouchUtils.emulateDoubleTapOnView(mInstrumentation, mTextView, spanDetails.mXPosInside,
spanDetails.mYPosInside);
- verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
+ verify(spanDetails.mClickableSpan, times(2)).onClick(mTextView);
}
@Test
@@ -6241,7 +6258,6 @@
ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
CtsTouchUtils.emulateTapOnView(mInstrumentation, mTextView, spanDetails.mXPosOutside,
spanDetails.mYPosOutside);
- SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 1);
verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
}
@@ -6643,7 +6659,137 @@
}
@Test
- public void testAutoSizeXY_getSetAutoSizeTextXY_defaults() {
+ public void testAutoSizeXY_obtainStyledAttributesUsingPredefinedSizes() {
+ DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
+ final TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(
+ R.id.textview_autosize_xy_predef_sizes);
+
+ // In arrays.xml predefined the step sizes as: 10px, 10dp, 10sp, 10pt, 10in and 10mm.
+ int[] expectedSizes = new int[] {
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 10f, metrics)),
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, metrics)),
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics)),
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 10f, metrics)),
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 10f, metrics)),
+ (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10f, metrics))};
+ expectedSizes = Arrays.stream(expectedSizes)
+ .filter(x -> x > 0)
+ .distinct()
+ .sorted()
+ .toArray();
+ assertArrayEquals(expectedSizes, autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+ boolean containsValueFromExpectedSizes = false;
+ int textSize = (int) autoSizeTextViewXY.getTextSize();
+ for (int i = 0; i < expectedSizes.length; i++) {
+ if (expectedSizes[i] == textSize) {
+ containsValueFromExpectedSizes = true;
+ break;
+ }
+ }
+ assertTrue(containsValueFromExpectedSizes);
+ }
+
+ @Test
+ public void testAutoSizeXY_obtainStyledAttributesPredefinedSizesFiltering() {
+ TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(
+ R.id.textview_autosize_xy_predef_sizes_redundant_values);
+
+ // In arrays.xml predefined the step sizes as: 40px, 10px, 10px, 10px, 0dp.
+ final int[] expectedSizes = new int[] {10, 40};
+ assertArrayEquals(expectedSizes, autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+ }
+
+ @Test
+ public void testAutoSizeXY_predefinedSizesFilteringAndSorting() throws Throwable {
+ mTextView = findTextView(R.id.textview_text);
+ assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+ final int[] predefinedSizes = new int[] {400, 0, 10, 40, 10, 10, 0, 0};
+ mActivityRule.runOnUiThread(() -> {
+ mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY);
+ mTextView.setAutoSizeTextPresetSizes(predefinedSizes);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertArrayEquals(new int[] {10, 40, 400}, mTextView.getAutoSizeTextPresetSizes());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testAutoSizeXY_predefinedSizesNullArray() throws Throwable {
+ mTextView = findTextView(R.id.textview_text);
+ assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+ final int[] predefinedSizes = null;
+ mActivityRule.runOnUiThread(() -> {
+ mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY);
+ mTextView.setAutoSizeTextPresetSizes(predefinedSizes);
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+
+ @Test
+ public void testAutoSizeXY_predefinedSizesEmptyArray() throws Throwable {
+ mTextView = findTextView(R.id.textview_text);
+ assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
+
+ mActivityRule.runOnUiThread(() ->
+ mTextView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_XY));
+ mInstrumentation.waitForIdleSync();
+
+ final int[] defaultSizes = mTextView.getAutoSizeTextPresetSizes();
+ assertNotNull(defaultSizes);
+ assertTrue(defaultSizes.length > 0);
+
+ final int[] predefinedSizes = new int[0];
+ mActivityRule.runOnUiThread(() ->
+ mTextView.setAutoSizeTextPresetSizes(predefinedSizes));
+ mInstrumentation.waitForIdleSync();
+
+ final int[] newSizes = mTextView.getAutoSizeTextPresetSizes();
+ assertNotNull(defaultSizes);
+ assertArrayEquals(defaultSizes, newSizes);
+ }
+
+ @Test
+ public void testAutoSizeXY_buildsSizes() throws Throwable {
+ TextView autoSizeTextViewXY = (TextView) mActivity.findViewById(R.id.textview_autosize_xy);
+
+ // Verify that the interval limits are both included.
+ mActivityRule.runOnUiThread(() -> {
+ autoSizeTextViewXY.setAutoSizeMinTextSize(TypedValue.COMPLEX_UNIT_PX, 10);
+ autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 20);
+ autoSizeTextViewXY.setAutoSizeStepGranularity(TypedValue.COMPLEX_UNIT_PX, 2);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertArrayEquals(
+ new int[] {10, 12, 14, 16, 18, 20},
+ autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+ mActivityRule.runOnUiThread(() -> {
+ autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 19);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertArrayEquals(
+ new int[] {10, 12, 14, 16, 18},
+ autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+
+ mActivityRule.runOnUiThread(() -> {
+ autoSizeTextViewXY.setAutoSizeMaxTextSize(TypedValue.COMPLEX_UNIT_PX, 21);
+ });
+ mInstrumentation.waitForIdleSync();
+ assertArrayEquals(
+ new int[] {10, 12, 14, 16, 18, 20},
+ autoSizeTextViewXY.getAutoSizeTextPresetSizes());
+ }
+
+ @Test
+ public void testAutoSizeXY_getSetAutoSizeTextXYDefaults() {
final TextView textView = new TextView(mActivity);
assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
// Min/Max/Granularity values for auto-sizing are 0 because they are not used.
@@ -6655,12 +6801,10 @@
assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_XY, textView.getAutoSizeTextType());
// Min/Max default values for auto-sizing XY have been loaded.
final int minSize = textView.getAutoSizeMinTextSize();
- assertNotEquals(0, minSize);
final int maxSize = textView.getAutoSizeMaxTextSize();
- assertNotEquals(0, maxSize);
+ assertTrue(0 < minSize);
assertTrue(minSize < maxSize);
- final int stepGranularity = textView.getAutoSizeStepGranularity();
- assertNotEquals(0, stepGranularity);
+ assertNotEquals(0, textView.getAutoSizeStepGranularity());
textView.setAutoSizeTextType(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
@@ -6785,6 +6929,8 @@
Point offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
CtsTouchUtils.emulateLongPressOnView(mInstrumentation, mTextView, offset.x, offset.y);
PollingCheck.waitFor(mTextView::hasSelection);
+ // Wait for smart selection. It times out after about 200ms.
+ SystemClock.sleep(300);
TextSelection selection = tcm.getDefaultTextClassifier()
.suggestSelection(text, startIndex, endIndex);
@@ -6792,6 +6938,84 @@
assertEquals(selection.getSelectionEndIndex(), mTextView.getSelectionEnd());
// TODO: Test the floating toolbar content.
}
+ @Test
+ public void testFontResources_setInXmlFamilyName() {
+ mTextView = findTextView(R.id.textview_fontresource_fontfamily);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testFontResourcesXml_setInXmlFamilyName() {
+ mTextView = findTextView(R.id.textview_fontxmlresource_fontfamily);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testFontResources_setInXmlStyle() {
+ mTextView = findTextView(R.id.textview_fontresource_style);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testFontResourcesXml_setInXmlStyle() {
+ mTextView = findTextView(R.id.textview_fontxmlresource_style);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testFontResources_setInXmlTextAppearance() {
+ mTextView = findTextView(R.id.textview_fontresource_textAppearance);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testFontResourcesXml_setInXmlTextAppearance() {
+ mTextView = findTextView(R.id.textview_fontxmlresource_textAppearance);
+ Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
+
+ assertEquals(expected, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testSmartSelection_multiSelect() throws Throwable {
+ mTextView = findTextView(R.id.textview_text);
+ String text = "The president-elect, Filip, is coming to town tomorrow.";
+ int startIndex = text.indexOf("is coming to town");
+ int endIndex = startIndex + "is coming to town".length();
+ initTextViewForTypingOnUiThread();
+ TextClassificationManager tcm = mActivity.getSystemService(TextClassificationManager.class);
+ mActivityRule.runOnUiThread(() -> {
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(text, BufferType.EDITABLE);
+ mTextView.setTextClassifier(tcm.getDefaultTextClassifier());
+ });
+ mInstrumentation.waitForIdleSync();
+
+ Point start = getCenterPositionOfTextAt(mTextView, startIndex, startIndex);
+ Point end = getCenterPositionOfTextAt(mTextView, endIndex, endIndex);
+ int[] viewOnScreenXY = new int[2];
+ mTextView.getLocationOnScreen(viewOnScreenXY);
+ int startX = start.x + viewOnScreenXY[0];
+ int startY = start.y + viewOnScreenXY[1];
+ int offsetX = end.x - start.x;
+
+ CtsTouchUtils.emulateLongPressAndDragGesture(
+ mInstrumentation, startX, startY, offsetX, 0 /* offsetY */);
+
+ // No smart selection when multiple words are selected.
+ assertEquals(startIndex, mTextView.getSelectionStart());
+ assertEquals(endIndex, mTextView.getSelectionEnd());
+ }
/**
* Some TextView attributes require non-fixed width and/or layout height. This function removes
diff --git a/tools/cts-tradefed/Android.mk b/tools/cts-tradefed/Android.mk
index bb4b934..7783e58c 100644
--- a/tools/cts-tradefed/Android.mk
+++ b/tools/cts-tradefed/Android.mk
@@ -25,7 +25,7 @@
LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
LOCAL_SUITE_NAME := CTS
LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
-LOCAL_SUITE_VERSION := 7.1_r1
+LOCAL_SUITE_VERSION := 7.1_r2
LOCAL_MODULE := cts-tradefed