[automerger skipped] [RESTRICT AUTOMERGE] CTS test for Android Security b/74016277 am: f59ed6d343 am: fa0756086d am: eaf4379de1 -s ours am: 1821301f87 -s ours am: 4097cf4f3e am: a85680f309 -s ours am: 56500459eb am: 977d1635dc
Original change: https://googleplex-android-review.googlesource.com/c/platform/cts/+/13031090
Change-Id: I6f111060498bb7f3c3a6ea6d2bd771fd9cbd5fe7
diff --git a/apps/CtsVerifier/res/layout/nls_item.xml b/apps/CtsVerifier/res/layout/nls_item.xml
index f1a10bf..e80d333 100644
--- a/apps/CtsVerifier/res/layout/nls_item.xml
+++ b/apps/CtsVerifier/res/layout/nls_item.xml
@@ -14,41 +14,51 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
- <ImageView
- android:id="@+id/nls_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="10dip"
- android:contentDescription="@string/pass_button_text"
- android:padding="10dip"
- android:src="@drawable/fs_indeterminate" />
-
- <TextView
- android:id="@+id/nls_instructions"
- style="@style/InstructionsSmallFont"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_toRightOf="@id/nls_status"
- android:text="@string/nls_enable_service" />
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/nls_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="10dip"
+ android:contentDescription="@string/pass_button_text"
+ android:padding="10dip"
+ android:src="@drawable/fs_indeterminate" />
+
+ <TextView
+ android:id="@+id/nls_instructions"
+ style="@style/InstructionsSmallFont"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_toRightOf="@id/nls_status"
+ android:text="@string/nls_enable_service" />
+ </LinearLayout>
<Button
android:id="@+id/nls_action_button"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_below="@id/nls_instructions"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
- android:layout_toRightOf="@id/nls_status"
android:onClick="actionPressed"
+ android:layout_gravity="center_horizontal"
android:text="@string/nls_start_settings" />
-</RelativeLayout>
\ No newline at end of file
+ <LinearLayout
+ android:id="@+id/feedback"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 74d50fb..899750a 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2185,6 +2185,9 @@
and disabled, and that once enabled the service is able to receive notifications and
dismiss them.
</string>
+ <string name="nls_anr">This test checks that notifications are not sent with content that is
+ too long. If this test causes the test app to ANR, the test has failed.
+ </string>
<string name="msg_extras_preserved">Check that Message extras Bundle was preserved.</string>
<string name="conversation_section_ordering">If this device supports conversation notifications,
and groups them into a separate section from alerting and silent non-conversation
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index aa5e9c0..121534a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -257,8 +257,8 @@
return item;
}
- protected View createAutoItem(ViewGroup parent, int stringId) {
- View item = mInflater.inflate(R.layout.nls_item, parent, false);
+ protected ViewGroup createAutoItem(ViewGroup parent, int stringId) {
+ ViewGroup item = (ViewGroup) mInflater.inflate(R.layout.nls_item, parent, false);
TextView instructions = item.findViewById(R.id.nls_instructions);
instructions.setText(stringId);
View button = item.findViewById(R.id.nls_action_button);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index 12e2b93..31980af 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -51,7 +51,7 @@
public static final String JSON_STATS = "stats";
public static final String JSON_LAST_AUDIBLY_ALERTED = "last_audibly_alerted";
- ArrayList<String> mPosted = new ArrayList<String>();
+ ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
ArrayMap<String, JSONObject> mNotifications = new ArrayMap<>();
ArrayMap<String, String> mNotificationKeys = new ArrayMap<>();
ArrayList<String> mRemoved = new ArrayList<String>();
@@ -77,6 +77,15 @@
return mNotifications.values();
}
+ protected StatusBarNotification getPosted(String tag) {
+ for (StatusBarNotification sbn : mPosted) {
+ if (sbn.getTag().equals(tag)) {
+ return sbn;
+ }
+ }
+ return null;
+ }
+
protected String getKeyForTag(String tag) {
return mNotificationKeys.get(tag);
}
@@ -149,7 +158,7 @@
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
if (!mTestPackages.contains(sbn.getPackageName())) { return; }
Log.d(TAG, "posted: " + sbn.getTag());
- mPosted.add(sbn.getTag());
+ mPosted.add(sbn);
mPostedNotifications.add(sbn.getNotification());
JSONObject notification = new JSONObject();
try {
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 42ee9e1..1d704b0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -56,6 +56,8 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
import androidx.core.app.NotificationCompat;
@@ -117,6 +119,7 @@
tests.add(new IsEnabledTest());
tests.add(new ServiceStartedTest());
tests.add(new NotificationReceivedTest());
+ tests.add(new LongMessageTest());
tests.add(new DataIntactTest());
tests.add(new AudiblyAlertedTest());
tests.add(new DismissOneTest());
@@ -261,8 +264,73 @@
@Override
protected void test() {
- List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
- if (result.size() > 0 && result.contains(mTag1)) {
+ if (MockListener.getInstance().getPosted(mTag1) != null) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ }
+ }
+
+ private class LongMessageTest extends InteractiveTestCase {
+ private ViewGroup mParent;
+ @Override
+ protected View inflate(ViewGroup parent) {
+ mParent = createAutoItem(parent, R.string.nls_anr);
+ return mParent;
+ }
+
+ @Override
+ protected void setUp() {
+ createChannels();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 20000; i++) {
+ sb.append("\u2009\u200a" + "\u200E\u200F" + "stuff");
+ }
+ Notification.Builder builder = new Notification.Builder(
+ mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(android.R.id.icon)
+ .setContentTitle("This is an long notification")
+ .setContentText("Innocuous content")
+ .setStyle(new Notification.MessagingStyle("Fake person")
+ .addMessage("hey how is it goin", 0, "Person 1")
+ .addMessage("hey", 0, "Person 1")
+ .addMessage("u there", 0, "Person 1")
+ .addMessage("how you like tHIS", 0, "Person 1")
+ .addMessage(sb.toString(), 0, "Person 1")
+ );
+ mTag1 = UUID.randomUUID().toString();
+ mId1 = NOTIFICATION_ID + 1;
+ mPackageString = "com.android.cts.verifier";
+ mNm.notify(mTag1, mId1, builder.build());
+ status = READY;
+ }
+
+ @Override
+ protected void tearDown() {
+ mNm.cancelAll();
+ MockListener.getInstance().resetData();
+ deleteChannels();
+ }
+
+ @Override
+ protected void test() {
+ StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1);
+ if (sbn == null) {
+ logFail();
+ status = FAIL;
+ } else {
+ ViewGroup parent = mParent.findViewById(R.id.feedback);
+ parent.setVisibility(View.VISIBLE);
+ final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+ NotificationListenerVerifierActivity.this,
+ sbn.getNotification());
+ RemoteViews rv = recoveredBuilder.createContentView();
+ View v = rv.apply(NotificationListenerVerifierActivity.this, parent);
+ parent.addView(v);
+ }
+ if (MockListener.getInstance().getPosted(mTag1) != null) {
status = PASS;
} else {
logFail();
@@ -985,8 +1053,7 @@
if (MockListener.getInstance() == null) {
status = PASS;
} else {
- List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
- if (result.size() == 0) {
+ if (MockListener.getInstance().mPosted.size() == 0) {
status = PASS;
} else {
logFail();
@@ -1157,8 +1224,7 @@
state = READY_TO_CHECK_FOR_UNSNOOZE;
}
} else {
- List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
- if (result.size() > 0 && result.contains(mTag1)) {
+ if (MockListener.getInstance().getPosted(mTag1) != null) {
status = PASS;
} else {
logFail();
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
index 14f8285..03d4a50 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
@@ -197,4 +197,12 @@
public static String getSecureSetting(String key) {
return SystemUtil.runShellCommand("settings --user current get secure " + key).trim();
}
+
+ /**
+ * Get a global setting for the given user. Trims ending new line.
+ */
+ public static String getSecureSettingAsUser(int userId, String key) {
+ return SystemUtil.runShellCommand(
+ String.format("settings --user %d get secure %s", userId, key)).trim();
+ }
}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UserUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UserUtils.java
new file mode 100644
index 0000000..198e602
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UserUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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 android.os.SystemProperties;
+
+/**
+ * Provides utilities to deal with user status.
+ */
+public final class UserUtils {
+
+ private static final String TAG = UserUtils.class.getSimpleName();
+ private static final String SYS_PROP_HEADLESS_SYSTEM_USER = "ro.fw.mu.headless_system_user";
+
+ private UserUtils() {
+ throw new AssertionError("Should not be instantiated");
+ }
+
+ /**
+ * Tells if the device is in headless system user mode.
+ */
+ public static boolean isHeadlessSystemUserMode() {
+ return SystemProperties.getBoolean(SYS_PROP_HEADLESS_SYSTEM_USER, false);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
index fdbb439..20be1a7 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
@@ -23,6 +23,11 @@
created by com.android.cts.appwithdata.
-->
+ <!--
+ We want to test without app data isolation, which comes with targetSdk 30.
+ -->
+ <uses-sdk android:targetSdkVersion="29" />
+
<uses-permission android:name="android.permission.INTERNET"/>
<application>
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
index b1f804e..74d754d 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -28,10 +29,14 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,6 +50,9 @@
/**
* Test that another app's private data cannot be accessed, while its public data can.
*
+ * These tests are for apps targeting SDK 29 (or lower). Tests for the behavior for apps targeting
+ * SDK 30+ are in {@code AppDataIsolationTests}.
+ *
* Assumes that {@link #APP_WITH_DATA_PKG} has already created the private and public data.
*/
@RunWith(AndroidJUnit4.class)
@@ -70,10 +78,15 @@
private static final Uri PRIVATE_TARGET = Uri.parse("content://com.android.cts.appwithdata/");
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ }
+
/**
- * Tests that another app's private data cannot be accessed. It includes file
- * and detailed traffic stats.
- * @throws IOException
+ * Tests that another app's private data cannot be accessed.
*/
@Test
public void testAccessPrivateData() throws IOException {
@@ -90,17 +103,8 @@
}
}
- private ApplicationInfo getApplicationInfo(String packageName) {
- try {
- return InstrumentationRegistry.getContext().getPackageManager().getApplicationInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalStateException("Expected package not found: " + e);
- }
- }
-
/**
- * Tests that another app's public file can be accessed
- * @throws IOException
+ * Tests that another app's public file cannot be accessed
*/
@Test
public void testAccessPublicData() throws IOException {
@@ -117,6 +121,21 @@
}
}
+ /**
+ * Tests that we can't even access another app's root private data dir.
+ */
+ @Test
+ public void testStatPrivateDataDir() {
+ ApplicationInfo applicationInfo = getApplicationInfo(APP_WITH_DATA_PKG);
+ String path = applicationInfo.dataDir;
+ try {
+ Os.stat(path);
+ fail("Was able to stat() another app's private data dir: " + path);
+ } catch (ErrnoException expected) {
+ assertEquals(path, OsConstants.EACCES, expected.errno);
+ }
+ }
+
@Test
public void testAccessProcQtaguidTrafficStatsFailed() {
// For untrusted app with SDK P or above, proc/net/xt_qtaguid files are no long readable.
@@ -130,7 +149,7 @@
public void testAccessPrivateTrafficStats() {
int otherAppUid = -1;
try {
- otherAppUid = InstrumentationRegistry.getContext()
+ otherAppUid = mContext
.createPackageContext(APP_WITH_DATA_PKG, 0 /*flags*/)
.getApplicationInfo().uid;
} catch (NameNotFoundException e) {
@@ -159,7 +178,7 @@
final long txp = TrafficStats.getUidTxPackets(uid);
// Start remote server
- final int port = InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "start", null, null)
+ final int port = mContext.getContentResolver().call(PRIVATE_TARGET, "start", null, null)
.getInt("port");
// Try talking to them, but shift blame
@@ -169,7 +188,7 @@
Bundle extras = new Bundle();
extras.putParcelable("fd", ParcelFileDescriptor.fromSocket(socket));
- InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
+ mContext.getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
socket.connect(new InetSocketAddress("localhost", port));
@@ -181,7 +200,7 @@
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
- InstrumentationRegistry.getContext().getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
+ mContext.getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
}
SystemClock.sleep(1000);
@@ -192,4 +211,12 @@
assertEquals(txb, TrafficStats.getUidTxBytes(uid));
assertEquals(txp, TrafficStats.getUidTxPackets(uid));
}
+
+ private ApplicationInfo getApplicationInfo(String packageName) {
+ try {
+ return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Expected package not found: " + e);
+ }
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index cf85210..ea8a824 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -23,6 +23,12 @@
access.
-->
+ <!--
+ We want to test without app data isolation, which comes with targetSdk 30.
+ -->
+ <uses-sdk android:targetSdkVersion="29" />
+
+
<uses-permission android:name="android.permission.INTERNET" />
<application>
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
index b752b41..6ed0e60 100644
--- a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
@@ -18,6 +18,7 @@
import static android.media.cts.MediaSessionTestHelperConstants.MEDIA_SESSION_TEST_HELPER_PKG;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -25,6 +26,8 @@
import android.content.Context;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.os.Process;
import android.service.notification.NotificationListenerService;
import androidx.test.InstrumentationRegistry;
@@ -42,15 +45,16 @@
*/
@SmallTest
public class MediaSessionManagerTest extends NotificationListenerService {
- private ComponentName mComponentName;
+ private Context mContext;
private MediaSessionManager mMediaSessionManager;
+ private ComponentName mComponentName;
@Before
public void setUp() throws Exception {
- Context context = InstrumentationRegistry.getTargetContext();
- mMediaSessionManager = (MediaSessionManager) context.getSystemService(
- Context.MEDIA_SESSION_SERVICE);
- mComponentName = new ComponentName(context, MediaSessionManagerTest.class);
+ mContext = InstrumentationRegistry.getTargetContext();
+ mMediaSessionManager =
+ mContext.getSystemService(MediaSessionManager.class);
+ mComponentName = new ComponentName(mContext, MediaSessionManagerTest.class);
}
/**
@@ -95,4 +99,24 @@
List<MediaController> controllers = mMediaSessionManager.getActiveSessions(mComponentName);
assertTrue(controllers.isEmpty());
}
+
+ /**
+ * Tests if this application is trusted.
+ */
+ @Test
+ public void testIsTrusted_returnsTrue() throws Exception {
+ RemoteUserInfo userInfo = new RemoteUserInfo(
+ mContext.getPackageName(), Process.myPid(), Process.myUid());
+ assertTrue(mMediaSessionManager.isTrustedForMediaControl(userInfo));
+ }
+
+ /**
+ * Tests if this application isn't trusted.
+ */
+ @Test
+ public void testIsTrusted_returnsFalse() throws Exception {
+ RemoteUserInfo userInfo = new RemoteUserInfo(
+ mContext.getPackageName(), Process.myPid(), Process.myUid());
+ assertFalse(mMediaSessionManager.isTrustedForMediaControl(userInfo));
+ }
}
diff --git a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
index 750e98c..e4956b0 100644
--- a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
+++ b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
@@ -251,6 +251,37 @@
runTest("testGetActiveSessions_hasMediaSessionFromMediaSessionTestHelper");
}
+ @AppModeFull
+ @RequiresDevice
+ public void testIsTrusted_withEnabledNotificationListener_returnsTrue() throws Exception {
+ if (!canCreateAdditionalUsers(1)) {
+ CLog.logAndDisplay(LogLevel.INFO,
+ "Cannot create a new user. Skipping multi-user test cases.");
+ return;
+ }
+
+ int newUserId = createAndStartUser();
+ setAllowGetActiveSessionForTest(true, newUserId);
+ installAppAsUser(DEVICE_SIDE_TEST_APK, newUserId, false);
+ runTestAsUser("testIsTrusted_returnsTrue", newUserId);
+ }
+
+ @AppModeFull
+ @RequiresDevice
+ public void testIsTrusted_withoutEnabledNotificationListener_returnsFalse()
+ throws Exception {
+ if (!canCreateAdditionalUsers(1)) {
+ CLog.logAndDisplay(LogLevel.INFO,
+ "Cannot create a new user. Skipping multi-user test cases.");
+ return;
+ }
+
+ int newUserId = createAndStartUser();
+ setAllowGetActiveSessionForTest(false, newUserId);
+ installAppAsUser(DEVICE_SIDE_TEST_APK, newUserId, false);
+ runTestAsUser("testIsTrusted_returnsFalse", newUserId);
+ }
+
private void runTest(String testMethodName) throws DeviceNotAvailableException {
runTestAsUser(testMethodName, getDevice().getPrimaryUserId());
}
diff --git a/hostsidetests/mediaparser/Android.bp b/hostsidetests/mediaparser/Android.bp
new file mode 100644
index 0000000..87c9621
--- /dev/null
+++ b/hostsidetests/mediaparser/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright 2020 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.
+//
+
+java_test_host {
+ name: "CtsMediaParserHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ static_libs: [
+ "cts-host-utils",
+ ],
+ data: [
+ ":CtsMediaParserTestCasesApp",
+ ]
+}
diff --git a/hostsidetests/mediaparser/AndroidTest.xml b/hostsidetests/mediaparser/AndroidTest.xml
new file mode 100644
index 0000000..7d53939
--- /dev/null
+++ b/hostsidetests/mediaparser/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 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 CTS media host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="media" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsMediaParserHostTestCases.jar" />
+ <option name="runtime-hint" value="3m" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/mediaparser/OWNERS b/hostsidetests/mediaparser/OWNERS
new file mode 100644
index 0000000..51256bf
--- /dev/null
+++ b/hostsidetests/mediaparser/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 817235
+aquilescanta@google.com
+andrewlewis@google.com
+essick@google.com
+marcone@google.com
diff --git a/hostsidetests/mediaparser/src/android/media/mediaparser/cts/MediaParserHostSideTest.java b/hostsidetests/mediaparser/src/android/media/mediaparser/cts/MediaParserHostSideTest.java
new file mode 100644
index 0000000..7f34459
--- /dev/null
+++ b/hostsidetests/mediaparser/src/android/media/mediaparser/cts/MediaParserHostSideTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2020 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.media.mediaparser.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.MediametricsMediaParserReported;
+import com.android.os.StatsLog;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Test for checking that the MediaParser CTS tests produce the expected media metrics. */
+// TODO(b/172494357): Re-enable tests once we can check for the mainline train version.
+public class MediaParserHostSideTest extends DeviceTestCase implements IBuildReceiver {
+
+ private static final String MEDIAPARSER_TEST_APK = "CtsMediaParserTestCasesApp.apk";
+ private static final String MEDIAPARSER_TEST_APP_PACKAGE = "android.media.mediaparser.cts";
+ private static final String MEDIAPARSER_TEST_CLASS_NAME =
+ "android.media.mediaparser.cts.MediaParserTest";
+ private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+ private static final long CONFIG_ID = "cts_config".hashCode();
+ private static final String MEDIAPARSER_METRICS_SEPARATOR = "\\|";
+ private static final double MEDIAPARSER_METRICS_DITHER_VALUE = .02f;
+
+ private IBuildInfo mCtsBuildInfo;
+
+ // Resource management.
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuildInfo = buildInfo;
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ File apk = new CompatibilityBuildHelper(mCtsBuildInfo).getTestFile(MEDIAPARSER_TEST_APK);
+ assertThat(getDevice().installPackage(apk, /* reinstall= */ true)).isNull();
+ removeConfig();
+ createAndUploadConfig();
+ getAndClearReportList(); // Clear existing reports.
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ removeConfig();
+ getDevice().uninstallPackage(MEDIAPARSER_TEST_APP_PACKAGE);
+ }
+
+ // Tests.
+
+ public void ignored_testCreationByNameMetrics() throws Exception {
+ runDeviceTest("testCreationByName");
+ String[] expectedParserNames = {
+ "android.media.mediaparser.MatroskaParser",
+ "android.media.mediaparser.FragmentedMp4Parser",
+ "android.media.mediaparser.Mp4Parser",
+ "android.media.mediaparser.Mp3Parser",
+ "android.media.mediaparser.AdtsParser",
+ "android.media.mediaparser.Ac3Parser",
+ "android.media.mediaparser.TsParser",
+ "android.media.mediaparser.FlvParser",
+ "android.media.mediaparser.OggParser",
+ "android.media.mediaparser.PsParser",
+ "android.media.mediaparser.WavParser",
+ "android.media.mediaparser.AmrParser",
+ "android.media.mediaparser.Ac4Parser",
+ "android.media.mediaparser.FlacParser",
+ };
+ // All of the above are created by name.
+ int[] expectedCreatedByName =
+ Arrays.stream(expectedParserNames).mapToInt(unusedArgument -> 1).toArray();
+ runDeviceTest("testCreationByName");
+ List<MediametricsMediaParserReported> mediaParserReportedEvents =
+ getMediaParserReportedEvents();
+ String[] observedParserNames =
+ mediaParserReportedEvents.stream()
+ .map(MediametricsMediaParserReported::getParserName)
+ .toArray(String[]::new);
+ int[] observedCreatedByName =
+ mediaParserReportedEvents.stream()
+ .mapToInt(MediametricsMediaParserReported::getCreatedByName)
+ .toArray();
+ assertThat(observedParserNames).isEqualTo(expectedParserNames);
+ assertThat(observedCreatedByName).isEqualTo(expectedCreatedByName);
+ }
+
+ public void ignored_testParserPool() throws Exception {
+ runDeviceTest("testMp4");
+ String[] expectedParserNamesInPool = {
+ "android.media.mediaparser.MatroskaParser",
+ "android.media.mediaparser.FragmentedMp4Parser",
+ "android.media.mediaparser.Mp4Parser",
+ "android.media.mediaparser.Mp3Parser",
+ "android.media.mediaparser.AdtsParser",
+ "android.media.mediaparser.Ac3Parser",
+ "android.media.mediaparser.TsParser",
+ "android.media.mediaparser.FlvParser",
+ "android.media.mediaparser.OggParser",
+ "android.media.mediaparser.PsParser",
+ "android.media.mediaparser.WavParser",
+ "android.media.mediaparser.AmrParser",
+ "android.media.mediaparser.Ac4Parser",
+ "android.media.mediaparser.FlacParser",
+ };
+ String parserPool = getSingleMediaParserReportedEvent().getParserPool();
+ List<String> parserNamesInParserPool =
+ Arrays.asList(parserPool.split(MEDIAPARSER_METRICS_SEPARATOR));
+ // We do not assert the order in the pool in order to allow test robustness against future
+ // mainline changes.
+ assertThat(parserNamesInParserPool).containsExactlyElementsIn(expectedParserNamesInPool);
+ }
+
+ public void ignored_testLastException() throws Exception {
+ runDeviceTest("testOggInvalidHeaderSniff");
+ List<MediametricsMediaParserReported> mediaParserReportedEvents =
+ getMediaParserReportedEvents();
+ assertThat(mediaParserReportedEvents).hasSize(2);
+ for (MediametricsMediaParserReported event : mediaParserReportedEvents) {
+ assertThat(event.getLastException())
+ .isEqualTo("android.media.MediaParser$UnrecognizedInputFormatException");
+ }
+ }
+
+ public void ignored_testResourceByteCount() throws Exception {
+ long actualInputSize = 101597;
+ long minimumExpectedResourceByteCount =
+ (long) (actualInputSize * (1 - MEDIAPARSER_METRICS_DITHER_VALUE));
+ long maximumExpectedResourceByteCount =
+ (long) (actualInputSize * (1 + MEDIAPARSER_METRICS_DITHER_VALUE));
+ runDeviceTest("testMp4");
+ long reportedByteCount = getSingleMediaParserReportedEvent().getResourceByteCount();
+ assertThat(reportedByteCount).isAtLeast(minimumExpectedResourceByteCount);
+ assertThat(reportedByteCount).isAtMost(maximumExpectedResourceByteCount);
+ }
+
+ public void ignored_testDurationMillis() throws Exception {
+ long actualDurationMillis = 1024;
+ long minimumExpectedResourceByteCount =
+ (long) (actualDurationMillis * (1 - MEDIAPARSER_METRICS_DITHER_VALUE));
+ long maximumExpectedResourceByteCount =
+ (long) (actualDurationMillis * (1 + MEDIAPARSER_METRICS_DITHER_VALUE));
+ runDeviceTest("testMp4");
+ long reportedDurationMillis = getSingleMediaParserReportedEvent().getDurationMillis();
+ assertThat(reportedDurationMillis).isAtLeast(minimumExpectedResourceByteCount);
+ assertThat(reportedDurationMillis).isAtMost(maximumExpectedResourceByteCount);
+ }
+
+ public void ignored_testTrackMimeTypes() throws Exception {
+ String[] expectedTrackMimeTypes = new String[] {"video/avc", "audio/mp4a-latm"};
+ runDeviceTest("testMp4");
+ String trackMimeTypesField = getSingleMediaParserReportedEvent().getTrackMimeTypes();
+ List<String> actualTrackMimeTypes =
+ Arrays.asList(trackMimeTypesField.split(MEDIAPARSER_METRICS_SEPARATOR));
+ assertThat(actualTrackMimeTypes).containsExactlyElementsIn(expectedTrackMimeTypes);
+ }
+
+ public void ignored_testTrackCodecs() throws Exception {
+ String[] expectedCodecs = new String[] {"", "mp4a.40.2"};
+ runDeviceTest("testMp4");
+ String trackMimeTypesField = getSingleMediaParserReportedEvent().getTrackCodecs();
+ List<String> actualTrackMimeTypes =
+ Arrays.asList(trackMimeTypesField.split(MEDIAPARSER_METRICS_SEPARATOR));
+ assertThat(actualTrackMimeTypes).containsExactlyElementsIn(expectedCodecs);
+ }
+
+ public void ignored_testAlteredParameters() throws Exception {
+ runDeviceTest("testTsWithH264DtsAudio");
+ assertThat(getSingleMediaParserReportedEvent().getAlteredParameters())
+ .isEqualTo("android.media.mediaparser.ts.enableHdmvDtsAudioStreams");
+ }
+
+ public void ignored_testVideoSize() throws Exception {
+ runDeviceTest("testMp4");
+ MediametricsMediaParserReported reportedEvent = getSingleMediaParserReportedEvent();
+ assertThat(reportedEvent.getVideoWidth()).isEqualTo(1080);
+ assertThat(reportedEvent.getVideoHeight()).isEqualTo(720);
+ }
+
+ // Internal methods.
+
+ /** Creates the statsd config and passes it to statsd. */
+ private void createAndUploadConfig() throws Exception {
+ StatsdConfig.Builder configBuilder =
+ StatsdConfigProto.StatsdConfig.newBuilder()
+ .setId(CONFIG_ID)
+ .addAllowedLogSource(MEDIAPARSER_TEST_APP_PACKAGE)
+ .addWhitelistedAtomIds(
+ AtomsProto.Atom.MEDIAMETRICS_MEDIAPARSER_REPORTED_FIELD_NUMBER);
+ addAtomEvent(configBuilder);
+ uploadConfig(configBuilder.build());
+ }
+
+ /** Removes any existing config with id {@link #CONFIG_ID}. */
+ private void removeConfig() throws Exception {
+ getDevice().executeShellCommand("cmd stats config remove " + CONFIG_ID);
+ }
+
+ /** Writes the given config into a file and passes is to statsd via standard input. */
+ private void uploadConfig(StatsdConfig config) throws Exception {
+ File configFile = File.createTempFile("statsdconfig", ".config");
+ configFile.deleteOnExit();
+ Files.write(config.toByteArray(), configFile);
+ String remotePath = "/data/local/tmp/" + configFile.getName();
+ // Make sure a config file with the same name doesn't exist already.
+ getDevice().deleteFile(remotePath);
+ assertThat(getDevice().pushFile(configFile, remotePath)).isTrue();
+ getDevice()
+ .executeShellCommand(
+ "cat " + remotePath + " | cmd stats config update " + CONFIG_ID);
+ getDevice().deleteFile(remotePath);
+ }
+
+ /**
+ * Asserts that there is only one MediaParser reported metric event, and returns it.
+ *
+ * <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
+ */
+ private MediametricsMediaParserReported getSingleMediaParserReportedEvent() throws Exception {
+ List<MediametricsMediaParserReported> mediaParserReportedEvents =
+ getMediaParserReportedEvents();
+ assertThat(mediaParserReportedEvents).hasSize(1);
+ return mediaParserReportedEvents.get(0);
+ }
+
+ /**
+ * Returns all MediaParser reported metric events sorted by timestamp.
+ *
+ * <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
+ */
+ private List<MediametricsMediaParserReported> getMediaParserReportedEvents() throws Exception {
+ ConfigMetricsReportList reportList = getAndClearReportList();
+ assertThat(reportList.getReportsCount()).isEqualTo(1);
+ StatsLog.ConfigMetricsReport report = reportList.getReports(0);
+ ArrayList<EventMetricData> data = new ArrayList<>();
+ report.getMetricsList()
+ .forEach(
+ statsLogReport ->
+ data.addAll(statsLogReport.getEventMetrics().getDataList()));
+ // We sort the reported events by the elapsed timestamp so as to ensure they are returned
+ // in the same order as they were generated by the CTS tests.
+ return data.stream()
+ .sorted(Comparator.comparing(EventMetricData::getElapsedTimestampNanos))
+ .map(event -> event.getAtom().getMediametricsMediaparserReported())
+ .collect(Collectors.toList());
+ }
+
+ /** Gets a statsd report and removes it from the device. */
+ private ConfigMetricsReportList getAndClearReportList() throws Exception {
+ CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+ getDevice()
+ .executeShellCommand(
+ "cmd stats dump-report " + CONFIG_ID + " --include_current_bucket --proto",
+ receiver);
+ return ConfigMetricsReportList.parser().parseFrom(receiver.getOutput());
+ }
+
+ /** Runs the test with the given name from the MediaParser CTS apk. */
+ private void runDeviceTest(String testMethodName) throws DeviceNotAvailableException {
+ RemoteAndroidTestRunner testRunner =
+ new RemoteAndroidTestRunner(
+ MEDIAPARSER_TEST_APP_PACKAGE, TEST_RUNNER, getDevice().getIDevice());
+ testRunner.setMethodName(MEDIAPARSER_TEST_CLASS_NAME, testMethodName);
+ CollectingTestListener listener = new CollectingTestListener();
+ assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+ TestRunResult result = listener.getCurrentRunResults();
+ assertThat(result.isRunFailure()).isFalse();
+ assertThat(result.getNumTests()).isEqualTo(1);
+ assertThat(result.hasFailedTests()).isFalse();
+ }
+
+ /** Adds an event to the config in order to match MediaParser reported atoms. */
+ private static void addAtomEvent(StatsdConfig.Builder config) {
+ String atomName = "Atom" + System.nanoTime();
+ String eventName = "Event" + System.nanoTime();
+ SimpleAtomMatcher.Builder sam =
+ SimpleAtomMatcher.newBuilder()
+ .setAtomId(AtomsProto.Atom.MEDIAMETRICS_MEDIAPARSER_REPORTED_FIELD_NUMBER);
+ config.addAtomMatcher(
+ AtomMatcher.newBuilder().setId(atomName.hashCode()).setSimpleAtomMatcher(sam));
+ config.addEventMetric(
+ StatsdConfigProto.EventMetric.newBuilder()
+ .setId(eventName.hashCode())
+ .setWhat(atomName.hashCode()));
+ }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 72f8415..566dc08 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -115,6 +115,7 @@
"com.android.cts.device.statsd.emptyapp";
private static final String TEST_REMOTE_DIR = "/data/local/tmp/statsd";
private static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+ private static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when app starting/stopping.
@@ -2112,18 +2113,48 @@
AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
- createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false); // False: does not use attribution.
+ createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
getDevice().executeShellCommand(String.format(
- "am start -n '%s' -e %s %s",
- "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
- "action", ACTION_SHOW_APPLICATION_OVERLAY));
+ "am start -n '%s' -e %s %s",
+ "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
+ "action", ACTION_SHOW_APPLICATION_OVERLAY));
final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
List<EventMetricData> data = getEventMetricDataList();
- Function<Atom, Integer> appUsageStateFunction = atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+ Function<Atom, Integer> appUsageStateFunction =
+ atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+ popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.s
+ assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+ }
+
+ public void testAppForceStopUsageEvent() throws Exception {
+ Set<Integer> onStates = new HashSet<>(Arrays.asList(
+ AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
+ Set<Integer> offStates = new HashSet<>(Arrays.asList(
+ AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
+
+ List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+ createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
+ Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+ getDevice().executeShellCommand(String.format(
+ "am start -n '%s' -e %s %s",
+ "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
+ "action", ACTION_LONG_SLEEP_WHILE_TOP));
+ final int waitTime = EXTRA_WAIT_TIME_MS + 5_000;
+ Thread.sleep(waitTime);
+
+ getDevice().executeShellCommand(String.format(
+ "am force-stop %s",
+ "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity"));
+ Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+ List<EventMetricData> data = getEventMetricDataList();
+ Function<Atom, Integer> appUsageStateFunction =
+ atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.
assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
}
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 0e1ebb0..e7bf788 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -91,7 +91,7 @@
}
java_library {
- name: "cts-install-lib",
+ name: "cts-install-lib-java",
srcs: ["src/**/*.java"],
static_libs: ["androidx.test.rules", "compatibility-device-util-axt", "truth-prebuilt"],
sdk_version: "test_current",
@@ -110,3 +110,11 @@
":StagedInstallTestApexV3",
],
}
+
+android_library {
+ name: "cts-install-lib",
+ manifest: "AndroidManifest.xml",
+ static_libs: [
+ "cts-install-lib-java",
+ ],
+}
diff --git a/libs/install/AndroidManifest.xml b/libs/install/AndroidManifest.xml
new file mode 100644
index 0000000..e9f5b7d
--- /dev/null
+++ b/libs/install/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.cts.install.lib"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <queries>
+ <package android:name="com.android.cts.install.lib.testapp.A"/>
+ <package android:name="com.android.cts.install.lib.testapp.B"/>
+ <package android:name="com.android.cts.install.lib.testapp.C"/>
+ </queries>
+
+ <uses-sdk android:minSdkVersion="8"/>
+</manifest>
diff --git a/libs/install/testapp/ACrashingV2.xml b/libs/install/testapp/ACrashingV2.xml
index 0ec90cf..338a5b9 100644
--- a/libs/install/testapp/ACrashingV2.xml
+++ b/libs/install/testapp/ACrashingV2.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App A v2" android:forceQueryable="true">
+ <application android:label="Test App A v2">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity">
diff --git a/libs/install/testapp/Av1.xml b/libs/install/testapp/Av1.xml
index 5b47699..e9714fc 100644
--- a/libs/install/testapp/Av1.xml
+++ b/libs/install/testapp/Av1.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App A1" android:forceQueryable="true">
+ <application android:label="Test App A1">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Av2.xml b/libs/install/testapp/Av2.xml
index 9f2c21a..fd8afa0 100644
--- a/libs/install/testapp/Av2.xml
+++ b/libs/install/testapp/Av2.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App A2" android:forceQueryable="true">
+ <application android:label="Test App A2">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Av3.xml b/libs/install/testapp/Av3.xml
index d86aebd..a7839e3 100644
--- a/libs/install/testapp/Av3.xml
+++ b/libs/install/testapp/Av3.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App A3" android:forceQueryable="true">
+ <application android:label="Test App A3">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Bv1.xml b/libs/install/testapp/Bv1.xml
index f990713..403e7e2 100644
--- a/libs/install/testapp/Bv1.xml
+++ b/libs/install/testapp/Bv1.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App B1" android:forceQueryable="true">
+ <application android:label="Test App B1">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Bv2.xml b/libs/install/testapp/Bv2.xml
index 3bd7292..f030c3f 100644
--- a/libs/install/testapp/Bv2.xml
+++ b/libs/install/testapp/Bv2.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App B2" android:forceQueryable="true">
+ <application android:label="Test App B2">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
index 32f6989..edb69f9 100644
--- a/libs/install/testapp/Cv1.xml
+++ b/libs/install/testapp/Cv1.xml
@@ -23,7 +23,7 @@
<uses-sdk android:minSdkVersion="19" />
- <application android:label="Test App C1" android:forceQueryable="true">
+ <application android:label="Test App C1">
<receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
android:exported="true" />
<activity android:name="com.android.cts.install.lib.testapp.MainActivity">
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 827375b..bc5b110 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -33,7 +33,10 @@
"platformprotosnano",
"permission-test-util-lib"
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 645d233..465b633 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -33,6 +33,8 @@
<option name="test-file-name" value="CtsCantSaveState1.apk" />
<option name="test-file-name" value="CtsCantSaveState2.apk" />
<option name="test-file-name" value="NotificationDelegator.apk" />
+ <option name="test-file-name" value="NotificationProvider.apk" />
+ <option name="test-file-name" value="NotificationListener.apk" />
<option name="test-file-name" value="StorageDelegator.apk" />
<option name="test-file-name" value="CtsActivityManagerApi29.apk" />
</target_preparer>
diff --git a/tests/app/NotificationListener/Android.bp b/tests/app/NotificationListener/Android.bp
new file mode 100644
index 0000000..96a0c3c
--- /dev/null
+++ b/tests/app/NotificationListener/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "NotificationListener",
+ defaults: ["cts_support_defaults"],
+ srcs: [
+ "**/*.java",
+ "**/*.kt",
+ "src/com/android/test/notificationlistener/INotificationUriAccessService.aidl",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ ],
+ platform_apis: true,
+ sdk_version: "test_current",
+}
diff --git a/tests/app/NotificationListener/AndroidManifest.xml b/tests/app/NotificationListener/AndroidManifest.xml
new file mode 100644
index 0000000..f510637
--- /dev/null
+++ b/tests/app/NotificationListener/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.test.notificationlistener">
+ <application android:label="Notification Listener">
+
+ <service android:name=".NotificationUriAccessService"
+ android:exported="true"/>
+
+ <service android:name=".TestNotificationListener"
+ android:exported="true"
+ android:label="TestNotificationListener"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/tests/app/NotificationListener/res/layout/activity.xml b/tests/app/NotificationListener/res/layout/activity.xml
new file mode 100644
index 0000000..f001f29
--- /dev/null
+++ b/tests/app/NotificationListener/res/layout/activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/activity_description"
+ />
+
+</LinearLayout>
diff --git a/tests/app/NotificationListener/res/values/strings.xml b/tests/app/NotificationListener/res/values/strings.xml
new file mode 100644
index 0000000..e19d5bf
--- /dev/null
+++ b/tests/app/NotificationListener/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources>
+ <string name="activity_description">This app has a listener and an service used for tests</string>
+</resources>
\ No newline at end of file
diff --git a/tests/app/NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl b/tests/app/NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl
new file mode 100644
index 0000000..eb93179
--- /dev/null
+++ b/tests/app/NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 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.test.notificationlistener;
+
+interface INotificationUriAccessService {
+ void ensureNotificationListenerServiceConnected(boolean connected);
+ boolean isFileUriAccessible(in android.net.Uri uri);
+}
diff --git a/tests/app/NotificationListener/src/com/android/test/notificationlistener/NotificationUriAccessService.kt b/tests/app/NotificationListener/src/com/android/test/notificationlistener/NotificationUriAccessService.kt
new file mode 100644
index 0000000..5e6e469
--- /dev/null
+++ b/tests/app/NotificationListener/src/com/android/test/notificationlistener/NotificationUriAccessService.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 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.test.notificationlistener
+
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.net.Uri
+import android.os.IBinder
+import java.io.IOException
+
+class NotificationUriAccessService : Service() {
+ private inner class MyNotificationUriAccessService : INotificationUriAccessService.Stub() {
+ @Throws(IllegalStateException::class)
+ override fun ensureNotificationListenerServiceConnected(ensureConnected: Boolean) {
+ val nm = getSystemService(NotificationManager::class.java)!!
+ val testListener = TestNotificationListener.componentName
+ check(nm.isNotificationListenerAccessGranted(testListener) == ensureConnected) {
+ "$testListener has incorrect listener access; expected=$ensureConnected"
+ }
+ val listener = TestNotificationListener.instance
+ if (ensureConnected) {
+ check(listener != null) {
+ "$testListener has not been created, but should be connected"
+ }
+ }
+ val isConnected = listener?.isConnected ?: false
+ check(isConnected == ensureConnected) {
+ "$testListener has incorrect listener connection state; expected=$ensureConnected"
+ }
+ }
+
+ override fun isFileUriAccessible(uri: Uri?): Boolean {
+ try {
+ contentResolver.openAssetFile(uri!!, "r", null).use { return true }
+ } catch (e: SecurityException) {
+ return false
+ } catch (e: IOException) {
+ throw IllegalStateException("Exception without security error", e)
+ }
+ }
+ }
+
+ private val mBinder = MyNotificationUriAccessService()
+ override fun onBind(intent: Intent): IBinder? {
+ return mBinder
+ }
+}
\ No newline at end of file
diff --git a/tests/app/NotificationListener/src/com/android/test/notificationlistener/TestNotificationListener.kt b/tests/app/NotificationListener/src/com/android/test/notificationlistener/TestNotificationListener.kt
new file mode 100644
index 0000000..19c7d82
--- /dev/null
+++ b/tests/app/NotificationListener/src/com/android/test/notificationlistener/TestNotificationListener.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.test.notificationlistener
+
+import android.content.ComponentName
+import android.service.notification.NotificationListenerService
+
+class TestNotificationListener : NotificationListenerService() {
+ var isConnected = false
+
+ override fun onListenerConnected() {
+ super.onListenerConnected()
+ instance = this
+ isConnected = true
+ }
+
+ override fun onListenerDisconnected() {
+ super.onListenerDisconnected()
+ isConnected = false
+ }
+
+ companion object {
+ var instance: TestNotificationListener? = null
+ private set
+ val componentName: ComponentName by lazy {
+ val javaClass = TestNotificationListener::class.java
+ ComponentName(javaClass.getPackage().name, javaClass.name)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/app/NotificationProvider/Android.bp b/tests/app/NotificationProvider/Android.bp
new file mode 100644
index 0000000..26e69d7
--- /dev/null
+++ b/tests/app/NotificationProvider/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "NotificationProvider",
+ defaults: ["cts_support_defaults"],
+ srcs: ["**/*.java", "**/*.kt"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ platform_apis: true,
+ sdk_version: "current",
+}
diff --git a/tests/app/NotificationProvider/AndroidManifest.xml b/tests/app/NotificationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..09ae4b0
--- /dev/null
+++ b/tests/app/NotificationProvider/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.test.notificationprovider">
+ <application android:label="Notification Provider">
+ <activity android:name=".RichNotificationActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name=".AssetFileProvider"
+ android:authorities="com.android.test.notificationprovider.provider"
+ android:exported="false"
+ android:grantUriPermissions="true"
+ />
+
+ </application>
+</manifest>
diff --git a/tests/app/NotificationProvider/assets/background7.png b/tests/app/NotificationProvider/assets/background7.png
new file mode 100644
index 0000000..20c22f7
--- /dev/null
+++ b/tests/app/NotificationProvider/assets/background7.png
Binary files differ
diff --git a/tests/app/NotificationProvider/assets/background8.png b/tests/app/NotificationProvider/assets/background8.png
new file mode 100644
index 0000000..a7a593d
--- /dev/null
+++ b/tests/app/NotificationProvider/assets/background8.png
Binary files differ
diff --git a/tests/app/NotificationProvider/res/layout/activity.xml b/tests/app/NotificationProvider/res/layout/activity.xml
new file mode 100644
index 0000000..f001f29
--- /dev/null
+++ b/tests/app/NotificationProvider/res/layout/activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/activity_description"
+ />
+
+</LinearLayout>
diff --git a/tests/app/NotificationProvider/res/values/strings.xml b/tests/app/NotificationProvider/res/values/strings.xml
new file mode 100644
index 0000000..266ea53
--- /dev/null
+++ b/tests/app/NotificationProvider/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources>
+ <string name="activity_description">This app has a provider and posts notifications</string>
+</resources>
\ No newline at end of file
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt
new file mode 100644
index 0000000..af10f1b
--- /dev/null
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 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.test.notificationprovider
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.res.AssetFileDescriptor
+import android.database.Cursor
+import android.net.Uri
+
+class AssetFileProvider : ContentProvider() {
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
+ val assets = context?.assets
+ val filename = uri.lastPathSegment
+ if (mode == "r" && assets != null && filename != null) {
+ return assets.openFd(filename)
+ }
+ return super.openAssetFile(uri, mode)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getType(uri: Uri): String? {
+ return null
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int {
+ throw UnsupportedOperationException()
+ }
+}
\ No newline at end of file
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
new file mode 100644
index 0000000..4197760
--- /dev/null
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.test.notificationprovider
+
+import android.app.Activity
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Bundle
+
+/**
+ * Used by NotificationManagerTest for testing policy around content uris.
+ */
+class RichNotificationActivity : Activity() {
+ companion object {
+ const val NOTIFICATION_CHANNEL_ID = "NotificationManagerTest"
+ const val EXTRA_ACTION = "action"
+ const val ACTION_SEND_7 = "send-7"
+ const val ACTION_SEND_8 = "send-8"
+ const val ACTION_CANCEL_7 = "cancel-7"
+ const val ACTION_CANCEL_8 = "cancel-8"
+ }
+
+ enum class NotificationPreset(val id: Int) {
+ Preset7(7),
+ Preset8(8);
+
+ fun build(context: Context): Notification {
+ val extras = Bundle()
+ extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
+ "content://com.android.test.notificationprovider.provider/background$id.png")
+ return Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("Rich Notification #$id")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addExtras(extras)
+ .build()
+ }
+ }
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity)
+ when (intent?.extras?.getString(EXTRA_ACTION)) {
+ ACTION_SEND_7 -> sendNotification(NotificationPreset.Preset7)
+ ACTION_SEND_8 -> sendNotification(NotificationPreset.Preset8)
+ ACTION_CANCEL_7 -> cancelNotification(NotificationPreset.Preset7)
+ ACTION_CANCEL_8 -> cancelNotification(NotificationPreset.Preset8)
+ else -> {
+ // reset both
+ cancelNotification(NotificationPreset.Preset7)
+ cancelNotification(NotificationPreset.Preset8)
+ }
+ }
+ finish()
+ }
+
+ private val notificationManager by lazy { getSystemService(NotificationManager::class.java)!! }
+
+ private fun sendNotification(preset: NotificationPreset) {
+ notificationManager.createNotificationChannel(NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ "Notifications", NotificationManager.IMPORTANCE_DEFAULT))
+ notificationManager.notify(preset.id, preset.build(this))
+ }
+
+ private fun cancelNotification(preset: NotificationPreset) {
+ notificationManager.cancel(preset.id)
+ }
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 12a24c8..5a13eab 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -48,6 +48,7 @@
public static final int COMMAND_STOP_ACTIVITY = 11;
public static final int COMMAND_CREATE_FGSL_PENDING_INTENT = 12;
public static final int COMMAND_SEND_FGSL_PENDING_INTENT = 13;
+ public static final int COMMAND_BIND_FOREGROUND_SERVICE = 14;
public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
@@ -82,7 +83,7 @@
+ intent);
switch (command) {
case COMMAND_BIND_SERVICE:
- doBindService(context, intent);
+ doBindService(context, intent, SERVICE_NAME);
break;
case COMMAND_UNBIND_SERVICE:
doUnbindService(context, intent);
@@ -120,15 +121,18 @@
case COMMAND_SEND_FGSL_PENDING_INTENT:
doSendFgslPendingIntent(context, intent);
break;
+ case COMMAND_BIND_FOREGROUND_SERVICE:
+ doBindService(context, intent, FG_LOCATION_SERVICE_NAME);
+ break;
}
}
- private void doBindService(Context context, Intent commandIntent) {
+ private void doBindService(Context context, Intent commandIntent, String serviceName) {
String targetPackage = getTargetPackage(commandIntent);
int flags = getFlags(commandIntent);
Intent bindIntent = new Intent();
- bindIntent.setComponent(new ComponentName(targetPackage, SERVICE_NAME));
+ bindIntent.setComponent(new ComponentName(targetPackage, serviceName));
ServiceConnection connection = addServiceConnection(targetPackage);
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 9302e67..c982cd6 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -327,4 +327,43 @@
uid2Watcher.finish();
}
}
+
+
+ public void testFgsLocationStartFromBGWithBind() throws Exception {
+ ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP1, 0);
+ WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+ WAITFOR_MSEC);
+
+ try {
+ // Package1 is in BG state, bind FGSL in package1 first.
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ Bundle bundle = new Bundle();
+ bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+ // Then start FGSL in package1, it won't get location capability.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
+
+ // Package1 is in FGS state, but won't get location capability.
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE,
+ new Integer(PROCESS_CAPABILITY_NONE));
+
+ // unbind service.
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ // stop FGSL
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_CACHED_EMPTY,
+ new Integer(PROCESS_CAPABILITY_NONE));
+ } finally {
+ uid1Watcher.finish();
+ }
+ }
}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 029ed92..22c604b 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -75,13 +75,16 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.OperationApplicationException;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
@@ -91,6 +94,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -111,12 +115,12 @@
import android.util.Log;
import android.widget.RemoteViews;
-import androidx.test.InstrumentationRegistry;
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.FeatureUtil;
import com.android.compatibility.common.util.SystemUtil;
-
-import junit.framework.Assert;
+import com.android.test.notificationlistener.INotificationUriAccessService;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -133,11 +137,15 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/* This tests NotificationListenerService together with NotificationManager, as you need to have
* notifications to manipulate in order to test the listener service. */
public class NotificationManagerTest extends AndroidTestCase {
+ public static final String NOTIFICATIONPROVIDER = "com.android.test.notificationprovider";
+ public static final String RICH_NOTIFICATION_ACTIVITY =
+ "com.android.test.notificationprovider.RichNotificationActivity";
final String TAG = NotificationManagerTest.class.getSimpleName();
final boolean DEBUG = false;
static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
@@ -160,6 +168,7 @@
private List<String> mRuleIds;
private BroadcastReceiver mBubbleBroadcastReceiver;
private boolean mBubblesEnabledSettingToRestore;
+ private INotificationUriAccessService mNotificationUriAccessService;
@Override
protected void setUp() throws Exception {
@@ -195,7 +204,8 @@
// delay between tests so notifications aren't dropped by the rate limiter
try {
Thread.sleep(500);
- } catch(InterruptedException e) {}
+ } catch (InterruptedException e) {
+ }
}
@Override
@@ -222,8 +232,7 @@
suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
false);
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), false);
+ toggleListenerAccess(false);
toggleNotificationPolicyAccess(mContext.getPackageName(),
InstrumentationRegistry.getInstrumentation(), false);
@@ -237,17 +246,17 @@
setBubblesGlobal(mBubblesEnabledSettingToRestore);
}
- private boolean isNotificationCancelled(int id, boolean all) {
+ private void assertNotificationCancelled(int id, boolean all) {
for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
- StatusBarNotification sbn = findPostedNotification(id, all);
- if (sbn == null) return true;
+ StatusBarNotification sbn = findNotificationNoWait(id, all);
+ if (sbn == null) return;
try {
Thread.sleep(SHORT_WAIT_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- return false;
+ assertNull(findNotificationNoWait(id, all));
}
private void insertSingleContact(String name, String phone, String email, boolean starred) {
@@ -296,8 +305,8 @@
try {
Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phone));
- String[] projection = new String[] { ContactsContract.Contacts._ID,
- ContactsContract.Contacts.LOOKUP_KEY };
+ String[] projection = new String[]{ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.LOOKUP_KEY};
c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
if (c != null && c.getCount() > 0) {
c.moveToFirst();
@@ -320,25 +329,28 @@
private StatusBarNotification findPostedNotification(int id, boolean all) {
// notification is a bit asynchronous so it may take a few ms to appear in
// getActiveNotifications()
- // we will check for it for up to 300ms before giving up
- StatusBarNotification n = null;
- for (int tries = 3; tries-- > 0; ) {
- final StatusBarNotification[] sbns = getActiveNotifications(all);
- for (StatusBarNotification sbn : sbns) {
- Log.d(TAG, "Found " + sbn.getKey());
- if (sbn.getId() == id) {
- n = sbn;
- break;
- }
+ // we will check for it for up to 1000ms before giving up
+ for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+ StatusBarNotification n = findNotificationNoWait(id, all);
+ if (n != null) {
+ return n;
}
- if (n != null) break;
try {
- Thread.sleep(100);
+ Thread.sleep(SHORT_WAIT_TIME);
} catch (InterruptedException ex) {
// pass
}
}
- return n;
+ return findNotificationNoWait(id, all);
+ }
+
+ private StatusBarNotification findNotificationNoWait(int id, boolean all) {
+ for (StatusBarNotification sbn : getActiveNotifications(all)) {
+ if (sbn.getId() == id) {
+ return sbn;
+ }
+ }
+ return null;
}
private StatusBarNotification[] getActiveNotifications(boolean all) {
@@ -449,8 +461,7 @@
private void setUpNotifListener() {
try {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
mListener = TestNotificationListener.getInstance();
mListener.resetData();
assertNotNull(mListener);
@@ -471,16 +482,16 @@
if (data == null) {
data = new Notification.BubbleMetadata.Builder(pendingIntent,
- Icon.createWithResource(mContext, R.drawable.black))
+ Icon.createWithResource(mContext, R.drawable.black))
.build();
}
if (builder == null) {
builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.black)
- .setWhen(System.currentTimeMillis())
- .setContentTitle("notify#" + id)
- .setContentText("This is #" + id + "notification ")
- .setContentIntent(pendingIntent);
+ .setSmallIcon(R.drawable.black)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle("notify#" + id)
+ .setContentText("This is #" + id + "notification ")
+ .setContentIntent(pendingIntent);
}
builder.setBubbleMetadata(data);
@@ -523,7 +534,7 @@
// getActiveNotifications()
// we will check for it for up to 300ms before giving up
boolean found = false;
- for (int tries = 3; tries--> 0;) {
+ for (int tries = 3; tries-- > 0; ) {
// Need reset flag.
found = false;
final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
@@ -549,7 +560,7 @@
// getActiveNotifications()
// we will check for it for up to 400ms before giving up
int lastCount = 0;
- for (int tries = 4; tries-- > 0;) {
+ for (int tries = 4; tries-- > 0; ) {
final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
lastCount = sbns.length;
if (expectedCount == lastCount) return;
@@ -559,7 +570,7 @@
// pass
}
}
- fail("Expected " + expectedCount + " posted notifications, were " + lastCount);
+ fail("Expected " + expectedCount + " posted notifications, were " + lastCount);
}
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
@@ -599,8 +610,8 @@
runCommand(command, instrumentation);
NotificationManager nm = mContext.getSystemService(NotificationManager.class);
- Assert.assertEquals("Notification Policy Access Grant is " +
- nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+ assertEquals("Notification Policy Access Grant is "
+ + nm.isNotificationPolicyAccessGranted() + " not " + on, on,
nm.isNotificationPolicyAccessGranted());
}
@@ -613,18 +624,23 @@
runCommand(command, instrumentation);
}
- private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
- boolean on) throws IOException {
-
+ private void toggleListenerAccess(boolean on) throws IOException {
String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
- + componentName;
+ + TestNotificationListener.getId();
- runCommand(command, instrumentation);
+ runCommand(command, InstrumentationRegistry.getInstrumentation());
final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
final ComponentName listenerComponent = TestNotificationListener.getComponentName();
- assertTrue(listenerComponent + " has not been granted access",
- nm.isNotificationListenerAccessGranted(listenerComponent) == on);
+ assertEquals(listenerComponent + " has incorrect listener access",
+ on, nm.isNotificationListenerAccessGranted(listenerComponent));
+ }
+
+ private void toggleExternalListenerAccess(ComponentName listenerComponent, boolean on)
+ throws IOException {
+ String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
+ + listenerComponent.flattenToString();
+ runCommand(command, InstrumentationRegistry.getInstrumentation());
}
private void setBubblesGlobal(boolean enabled)
@@ -656,15 +672,18 @@
Thread.sleep(500); // wait for ranking update
}
+ @SuppressWarnings("StatementWithEmptyBody")
private void runCommand(String command, Instrumentation instrumentation) throws IOException {
UiAutomation uiAutomation = instrumentation.getUiAutomation();
// Execute command
try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
- Assert.assertNotNull("Failed to execute shell command: " + command, fd);
+ assertNotNull("Failed to execute shell command: " + command, fd);
// Wait for the command to finish by reading until EOF
try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
byte[] buffer = new byte[4096];
- while (in.read(buffer) > 0) {}
+ while (in.read(buffer) > 0) {
+ // discard output
+ }
} catch (IOException e) {
throw new IOException("Could not read stdout of command: " + command, e);
}
@@ -698,7 +717,7 @@
private void assertExpectedDndState(int expectedState) {
int tries = 3;
- for (int i = tries; i >=0; i--) {
+ for (int i = tries; i >= 0; i--) {
if (expectedState ==
mNotificationManager.getCurrentInterruptionFilter()) {
break;
@@ -808,11 +827,11 @@
KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
keyguardManager.requestDismissKeyguard(sendBubbleActivity,
new KeyguardManager.KeyguardDismissCallback() {
- @Override
- public void onDismissSucceeded() {
- latch.countDown();
- }
- });
+ @Override
+ public void onDismissSucceeded() {
+ latch.countDown();
+ }
+ });
try {
latch.await(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
@@ -1098,7 +1117,7 @@
new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
channel.setDescription("bananas");
channel.enableVibration(true);
- channel.setVibrationPattern(new long[] {5, 8, 2, 1});
+ channel.setVibrationPattern(new long[]{5, 8, 2, 1});
channel.setSound(new Uri.Builder().scheme("test").build(),
new AudioAttributes.Builder().setUsage(
AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED).build());
@@ -1213,7 +1232,8 @@
try {
mNotificationManager.createNotificationChannel(channel);
fail("Created notification with bad group");
- } catch (IllegalArgumentException e) {}
+ } catch (IllegalArgumentException e) {
+ }
}
public void testCreateChannelInvalidImportance() throws Exception {
@@ -1380,8 +1400,7 @@
}
public void testSuspendPackage() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1422,8 +1441,7 @@
}
public void testSuspendedPackageSendsNotification() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1475,8 +1493,7 @@
assertEquals(1, Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES));
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1520,8 +1537,7 @@
assertEquals(1, Settings.Secure.getInt(
mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING));
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1563,8 +1579,7 @@
}
public void testGetSuppressedVisualEffectsOff_ranking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1592,8 +1607,7 @@
final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
NotificationManager.Policy origPolicy = mNotificationManager.getNotificationPolicy();
try {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1641,8 +1655,7 @@
}
public void testKeyChannelGroupOverrideImportanceExplanation_ranking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1934,7 +1947,8 @@
.setStyle(new Notification.BigPictureStyle()
.setBigContentTitle("title")
.bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
- .bigLargeIcon(Icon.createWithResource(getContext(), R.drawable.icon_blue))
+ .bigLargeIcon(
+ Icon.createWithResource(getContext(), R.drawable.icon_blue))
.setSummaryText("summary"))
.build();
mNotificationManager.notify(id, notification);
@@ -2365,31 +2379,31 @@
}
public void testNotificationPolicyVisualEffectsEqual() {
- NotificationManager.Policy policy = new NotificationManager.Policy(0,0 ,0 ,
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON);
- NotificationManager.Policy policy2 = new NotificationManager.Policy(0,0 ,0 ,
+ NotificationManager.Policy policy2 = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_PEEK);
assertTrue(policy.equals(policy2));
assertTrue(policy2.equals(policy));
- policy = new NotificationManager.Policy(0,0 ,0 ,
+ policy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON);
- policy2 = new NotificationManager.Policy(0,0 ,0 ,
+ policy2 = new NotificationManager.Policy(0, 0, 0,
0);
assertFalse(policy.equals(policy2));
assertFalse(policy2.equals(policy));
- policy = new NotificationManager.Policy(0,0 ,0 ,
+ policy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_OFF);
- policy2 = new NotificationManager.Policy(0,0 ,0 ,
+ policy2 = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_FULL_SCREEN_INTENT | SUPPRESSED_EFFECT_AMBIENT
| SUPPRESSED_EFFECT_LIGHTS);
assertTrue(policy.equals(policy2));
assertTrue(policy2.equals(policy));
- policy = new NotificationManager.Policy(0,0 ,0 ,
+ policy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_OFF);
- policy2 = new NotificationManager.Policy(0,0 ,0 ,
+ policy2 = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_LIGHTS);
assertFalse(policy.equals(policy2));
assertFalse(policy2.equals(policy));
@@ -2440,7 +2454,7 @@
mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
assertNotNull(findPostedNotification(10000, false));
mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
- assertTrue(isNotificationCancelled(10000, false));
+ assertNotificationCancelled(10000, false);
final Intent revokeIntent = new Intent();
revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2450,8 +2464,7 @@
public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2470,7 +2483,7 @@
try {
mNotificationManager.cancelAsPackage(DELEGATOR, null, 9);
- fail ("Delegate should not be able to cancel notification they did not post");
+ fail("Delegate should not be able to cancel notification they did not post");
} catch (SecurityException e) {
// yay
}
@@ -2618,8 +2631,7 @@
// pass
}
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
// no exception this time
mNotificationManager.shouldHideSilentStatusBarIcons();
}
@@ -2681,9 +2693,250 @@
listener.onListenerDisconnected();
}
+ private void performNotificationProviderAction(@NonNull String action) {
+ // Create an intent to launch an activity which just posts or cancels notifications
+ Intent activityIntent = new Intent();
+ activityIntent.setClassName(NOTIFICATIONPROVIDER, RICH_NOTIFICATION_ACTIVITY);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityIntent.putExtra("action", action);
+ mContext.startActivity(activityIntent);
+ }
+
+ public void testNotificationUriPermissionsGranted() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+ Uri background8Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background8.png");
+
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertNotificationCancelled(8, true);
+ assertAccessible(background7Uri);
+ assertInaccessible(background8Uri);
+
+ // Post #8
+ performNotificationProviderAction("send-8");
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
+ assertAccessible(background7Uri);
+ assertAccessible(background8Uri);
+
+ // Cancel #7
+ performNotificationProviderAction("cancel-7");
+
+ assertNotificationCancelled(7, true);
+ assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
+ assertInaccessible(background7Uri);
+ assertAccessible(background8Uri);
+
+ // Cancel #8
+ performNotificationProviderAction("cancel-8");
+
+ assertNotificationCancelled(7, true);
+ assertNotificationCancelled(8, true);
+ assertInaccessible(background7Uri);
+ assertInaccessible(background8Uri);
+
+ } finally {
+ // Clean up -- reset any remaining notifications
+ performNotificationProviderAction("reset");
+ Thread.sleep(500);
+ }
+ }
+
+ public void testNotificationUriPermissionsGrantedToNewListeners() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ Thread.sleep(500);
+ // Don't have access the notification yet, but we can test the URI
+ assertInaccessible(background7Uri);
+
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertAccessible(background7Uri);
+
+ } finally {
+ // Clean Up -- Cancel #7
+ performNotificationProviderAction("cancel-7");
+ Thread.sleep(500);
+ }
+ }
+
+ public void testNotificationUriPermissionsRevokedFromRemovedListeners() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertAccessible(background7Uri);
+
+ // Remove the listener to ensure permissions get revoked
+ toggleListenerAccess(false);
+ Thread.sleep(500); // wait for listener to be disabled
+
+ assertInaccessible(background7Uri);
+
+ } finally {
+ // Clean Up -- Cancel #7
+ performNotificationProviderAction("cancel-7");
+ Thread.sleep(500);
+ }
+ }
+
+ private class NotificationListenerConnection implements ServiceConnection {
+ private final Semaphore mSemaphore = new Semaphore(0);
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mNotificationUriAccessService = INotificationUriAccessService.Stub.asInterface(service);
+ mSemaphore.release();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mNotificationUriAccessService = null;
+ }
+
+ public void waitForService() {
+ try {
+ if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+ return;
+ }
+ } catch (InterruptedException e) {
+ }
+ fail("failed to connec to service");
+ }
+ }
+
+ ;
+
+ public void testNotificationUriPermissionsRevokedOnlyFromRemovedListeners() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+
+ // Connect to a service in the NotificationListener app which allows us to validate URI
+ // permissions granted to a second app, so that we show that permissions aren't being
+ // revoked too broadly.
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.test.notificationlistener",
+ "com.android.test.notificationlistener.NotificationUriAccessService"));
+ NotificationListenerConnection connection = new NotificationListenerConnection();
+ mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ connection.waitForService();
+
+ // Before starting the test, make sure the service works, that there is no listener, and
+ // that the URI starts inaccessible to that process.
+ mNotificationUriAccessService.ensureNotificationListenerServiceConnected(false);
+ assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
+
+ // Give the NotificationListener app access to notifications, and validate that.
+ toggleExternalListenerAccess(new ComponentName("com.android.test.notificationlistener",
+ "com.android.test.notificationlistener.TestNotificationListener"), true);
+ Thread.sleep(500);
+ mNotificationUriAccessService.ensureNotificationListenerServiceConnected(true);
+ assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
+
+ // Give the test app access to notifications, and get that listener
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ try {
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ // Check that both the test app (this code) and the external app have URI access.
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertAccessible(background7Uri);
+ assertTrue(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
+
+ // Remove the listener to ensure permissions get revoked
+ toggleListenerAccess(false);
+ Thread.sleep(500); // wait for listener to be disabled
+
+ // Ensure that revoking listener access to this one app does not effect the other.
+ assertInaccessible(background7Uri);
+ assertTrue(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
+
+ } finally {
+ // Clean Up -- Cancel #7
+ performNotificationProviderAction("cancel-7");
+ Thread.sleep(500);
+ }
+
+ // Finally, cancelling the permission must still revoke those other permissions.
+ assertFalse(mNotificationUriAccessService.isFileUriAccessible(background7Uri));
+
+ } finally {
+ // Clean Up -- Make sure the external listener is has access revoked
+ toggleExternalListenerAccess(new ComponentName("com.android.test.notificationlistener",
+ "com.android.test.notificationlistener.TestNotificationListener"), false);
+ }
+ }
+
+ private void assertAccessible(Uri uri)
+ throws IOException {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
+ assertNotNull(fd);
+ } catch (SecurityException e) {
+ throw new AssertionError("URI should be accessible: " + uri, e);
+ }
+ }
+
+ private void assertInaccessible(Uri uri)
+ throws IOException {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
+ fail("URI should be inaccessible: " + uri);
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @NonNull
+ private Uri getNotificationBackgroundImageUri(int notificationId) {
+ StatusBarNotification sbn = findPostedNotification(notificationId, true);
+ assertNotNull(sbn);
+ String imageUriString = sbn.getNotification().extras
+ .getString(Notification.EXTRA_BACKGROUND_IMAGE_URI);
+ assertNotNull(imageUriString);
+ return Uri.parse(imageUriString);
+ }
+
public void testNotificationListener_setNotificationsShown() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2699,8 +2952,7 @@
StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
mListener.setNotificationsShown(new String[]{ sbn1.getKey() });
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), false);
+ toggleListenerAccess(false);
Thread.sleep(500); // wait for listener to be disallowed
try {
mListener.setNotificationsShown(new String[]{ sbn2.getKey() });
@@ -2711,8 +2963,7 @@
}
public void testNotificationListener_getNotificationChannels() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2727,8 +2978,7 @@
}
public void testNotificationListener_getNotificationChannelGroups() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2742,8 +2992,7 @@
}
public void testNotificationListener_updateNotificationChannel() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2762,8 +3011,7 @@
}
public void testNotificationListener_getActiveNotifications() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2790,8 +3038,7 @@
public void testNotificationListener_getCurrentRanking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2804,8 +3051,7 @@
}
public void testNotificationListener_cancelNotifications() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index e877350..b474fb8 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -23,11 +23,13 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import android.test.AndroidTestCase;
import android.util.Log;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.UserUtils;
import junit.framework.Assert;
@@ -108,6 +110,11 @@
}
public void testNightModeYesPersisted() throws InterruptedException {
+ if (mUiModeManager.isNightModeLocked()) {
+ Log.i(TAG, "testNightModeYesPersisted skipped: night mode is locked");
+ return;
+ }
+
// Reset the mode to no if it is set to another value
setNightMode(UiModeManager.MODE_NIGHT_NO);
@@ -116,6 +123,11 @@
}
public void testNightModeAutoPersisted() throws InterruptedException {
+ if (mUiModeManager.isNightModeLocked()) {
+ Log.i(TAG, "testNightModeAutoPersisted skipped: night mode is locked");
+ return;
+ }
+
// Reset the mode to no if it is set to another value
setNightMode(UiModeManager.MODE_NIGHT_NO);
@@ -125,6 +137,7 @@
public void testNightModeAutoNotPersistedCarMode() {
if (mUiModeManager.isNightModeLocked()) {
+ Log.i(TAG, "testNightModeAutoNotPersistedCarMode skipped: night mode is locked");
return;
}
@@ -139,6 +152,7 @@
public void testNightModeInCarModeIsTransient() {
if (mUiModeManager.isNightModeLocked()) {
+ Log.i(TAG, "testNightModeInCarModeIsTransient skipped: night mode is locked");
return;
}
@@ -156,6 +170,8 @@
public void testNightModeToggleInCarModeDoesNotChangeSetting() {
if (mUiModeManager.isNightModeLocked()) {
+ Log.i(TAG, "testNightModeToggleInCarModeDoesNotChangeSetting skipped: "
+ + "night mode is locked");
return;
}
@@ -174,6 +190,8 @@
public void testNightModeInCarModeOnPowerSaveIsTransient() throws Throwable {
if (mUiModeManager.isNightModeLocked() || !BatteryUtils.isBatterySaverSupported()) {
+ Log.i(TAG, "testNightModeInCarModeOnPowerSaveIsTransient skipped: "
+ + "night mode is locked or battery saver is not supported");
return;
}
@@ -347,7 +365,7 @@
int storedModeInt = -1;
// Settings.Secure.UI_NIGHT_MODE
for (int i = 0; i < MAX_WAIT_TIME; i += WAIT_TIME_INCR) {
- String storedMode = SettingsUtils.getSecureSetting("ui_night_mode");
+ String storedMode = getUiNightModeFromSetting();
storedModeInt = Integer.parseInt(storedMode);
if (mode == storedModeInt) break;
try {
@@ -404,4 +422,10 @@
}
}
+ private String getUiNightModeFromSetting() {
+ String key = "ui_night_mode";
+ return UserUtils.isHeadlessSystemUserMode()
+ ? SettingsUtils.getSecureSettingAsUser(UserHandle.USER_SYSTEM, key)
+ : SettingsUtils.getSecureSetting(key);
+ }
}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index d3b5621..108e034 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -302,6 +302,7 @@
public static final String EXTRA_APP_CONFIG_INFO = "app_config_info";
public static final String EXTRA_CONFIG_INFO_IN_ON_CREATE = "config_info_in_on_create";
public static final String EXTRA_DISPLAY_REAL_SIZE = "display_real_size";
+ public static final String EXTRA_SYSTEM_RESOURCES_CONFIG_INFO = "sys_config_info";
}
/** Extra key constants for {@link android.server.wm.app.FontScaleActivity}. */
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
index dd1ad87..8ceeab2 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LandscapeOrientationActivity.java
@@ -19,9 +19,11 @@
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_APP_CONFIG_INFO;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_CONFIG_INFO_IN_ON_CREATE;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_DISPLAY_REAL_SIZE;
+import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_SYSTEM_RESOURCES_CONFIG_INFO;
import android.app.Application;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
@@ -50,6 +52,8 @@
// own display adjustments.
app.getSystemService(DisplayManager.class)
.getDisplay(Display.DEFAULT_DISPLAY)));
+ extras.putParcelable(EXTRA_SYSTEM_RESOURCES_CONFIG_INFO,
+ new ConfigInfo(Resources.getSystem()));
client.putExtras(extras);
});
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 859eb29..302c915 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -40,6 +40,7 @@
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_APP_CONFIG_INFO;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_CONFIG_INFO_IN_ON_CREATE;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_DISPLAY_REAL_SIZE;
+import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_SYSTEM_RESOURCES_CONFIG_INFO;
import static android.server.wm.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
@@ -59,6 +60,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -68,6 +70,7 @@
import android.server.wm.CommandSession.ConfigInfo;
import android.server.wm.CommandSession.SizeInfo;
import android.server.wm.TestJournalProvider.TestJournalContainer;
+import android.util.DisplayMetrics;
import android.view.Display;
import org.junit.Test;
@@ -469,6 +472,9 @@
final Point onCreateRealDisplaySize = extras.getParcelable(EXTRA_DISPLAY_REAL_SIZE);
final ConfigInfo onCreateConfigInfo = extras.getParcelable(EXTRA_CONFIG_INFO_IN_ON_CREATE);
final SizeInfo onCreateSize = onCreateConfigInfo.sizeInfo;
+ final ConfigInfo globalConfigInfo =
+ extras.getParcelable(EXTRA_SYSTEM_RESOURCES_CONFIG_INFO);
+ final SizeInfo globalSizeInfo = globalConfigInfo.sizeInfo;
assertEquals("The last reported size should be the same as the one from onCreate",
reportedSizes, onCreateConfigInfo.sizeInfo);
@@ -482,6 +488,10 @@
expectedRotation, onCreateConfigInfo.rotation);
assertEquals("The application should get the final display rotation in onCreate",
expectedRotation, appConfigInfo.rotation);
+ assertEquals("The orientation of application must be landscape",
+ ORIENTATION_LANDSCAPE, appConfigInfo.sizeInfo.orientation);
+ assertEquals("The orientation of system resources must be landscape",
+ ORIENTATION_LANDSCAPE, globalSizeInfo.orientation);
assertEquals("The activity should get the final display size in onCreate",
expectedRealDisplaySize, onCreateRealDisplaySize);
@@ -490,6 +500,13 @@
onCreateSize.displayWidth > onCreateSize.displayHeight);
assertEquals("The application should get the same orientation", isLandscape,
appConfigInfo.sizeInfo.displayWidth > appConfigInfo.sizeInfo.displayHeight);
+ assertEquals("The app display metrics must be landscape", isLandscape,
+ appConfigInfo.sizeInfo.metricsWidth > appConfigInfo.sizeInfo.metricsHeight);
+
+ final DisplayMetrics globalMetrics = Resources.getSystem().getDisplayMetrics();
+ assertEquals("The display metrics of system resources must be landscape",
+ new Point(globalMetrics.widthPixels, globalMetrics.heightPixels),
+ new Point(globalSizeInfo.metricsWidth, globalSizeInfo.metricsHeight));
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
index 6ad0dc1..bd415bf 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
@@ -23,6 +23,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -60,6 +61,18 @@
}
/**
+ * Tests that config_remoteInsetsControllerControlsSystemBars is not set to true for
+ * non-automotive devices.
+ */
+ @Test
+ public void testRemoteInsetsControllerNotControlSystemBarsForNonAutoDevies() {
+ assumeFalse(isCar());
+
+ assertFalse("Non auto devices should not set config_remoteInsetsControllerControlsSystemBars",
+ remoteInsetsControllerControlsSystemBars());
+ }
+
+ /**
* Tests that secondary display has override configuration set.
*/
@Test
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 6ca5fc9..f4e7d89 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -1169,6 +1169,11 @@
.getBoolean(android.R.bool.config_perDisplayFocusEnabled);
}
+ protected static boolean remoteInsetsControllerControlsSystemBars() {
+ return getInstrumentation().getTargetContext().getResources()
+ .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
+ }
+
/** @see ObjectTracker#manage(AutoCloseable) */
protected HomeActivitySession createManagedHomeActivitySession(ComponentName homeActivity) {
return mObjectTracker.manage(new HomeActivitySession(homeActivity));
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
index 4039a13..418d298 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
@@ -1023,6 +1023,12 @@
sizeInfo = new SizeInfo(display, metrics, config);
}
+ public ConfigInfo(Resources res) {
+ final DisplayMetrics metrics = res.getDisplayMetrics();
+ final Configuration config = res.getConfiguration();
+ sizeInfo = new SizeInfo(null /* display */, metrics, config);
+ }
+
@Override
public String toString() {
return "ConfigInfo: {displayId=" + displayId + " rotation=" + rotation
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 728d136..0d169e3 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -16,8 +16,14 @@
package android.view.inputmethod.cts;
+import static android.view.View.VISIBLE;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
@@ -75,6 +81,7 @@
@RunWith(AndroidJUnit4.class)
public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
@Rule
public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
@@ -407,6 +414,96 @@
}
}
+ @Test
+ public void testImeState_EditorDialogLostFocusAfterUnlocked_Unspecified() throws Exception {
+ runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
+ }
+
+ @Test
+ public void testImeState_EditorDialogLostFocusAfterUnlocked_Visible() throws Exception {
+ runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
+ }
+
+ @Test
+ public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysVisible() throws Exception {
+ runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ }
+
+ @Test
+ public void testImeState_EditorDialogLostFocusAfterUnlocked_Hidden() throws Exception {
+ runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
+ }
+
+ @Test
+ public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysHidden() throws Exception {
+ runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ }
+
+ private void runImeDoesntReshowAfterKeyguardTest(int softInputState) throws Exception {
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ // Launch a simple test activity
+ final TestActivity testActivity =
+ TestActivity.startSync(activity -> new LinearLayout(activity));
+
+ // Launch a dialog and show keyboard
+ final String marker = getTestMarker();
+ final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+ final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>();
+ TestUtils.runOnMainSync(() -> {
+ final EditText editText = new EditText(testActivity);
+ editText.setHint("focused editText");
+ editText.setPrivateImeOptions(marker);
+ editText.requestFocus();
+ final AlertDialog dialog = new AlertDialog.Builder(testActivity)
+ .setView(editText)
+ .create();
+ dialog.getWindow().setSoftInputMode(softInputState);
+ dialog.show();
+ editText.getWindowInsetsController().show(ime());
+ editTextRef.set(editText);
+ dialogRef.set(dialog);
+ });
+
+ TestUtils.waitOnMainUntil(() -> dialogRef.get().isShowing()
+ && editTextRef.get().hasFocus(), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+ expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+ View.VISIBLE, TIMEOUT);
+ expectImeVisible(TIMEOUT);
+
+ // Clear editor focus after screen-off
+ TestUtils.turnScreenOff();
+ TestUtils.waitOnMainUntil(() -> editTextRef.get().getWindowVisibility() != VISIBLE,
+ TIMEOUT);
+ expectEvent(stream, onFinishInputViewMatcher(true), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+ expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+ // Expect showSoftInput comes when system notify InsetsController to apply show IME
+ // insets after IME input target updated.
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+ notExpectEvent(stream, hideSoftInputMatcher(), NOT_EXPECT_TIMEOUT);
+ TestUtils.runOnMainSync(editTextRef.get()::clearFocus);
+
+ // Verify IME will invisible after device unlocked
+ TestUtils.turnScreenOn();
+ TestUtils.unlockScreen();
+ // Expect hideSoftInput and onFinishInputView will called by IMMS when the same window
+ // focused since the editText view focus has been cleared.
+ TestUtils.waitOnMainUntil(() -> editTextRef.get().hasWindowFocus()
+ && !editTextRef.get().hasFocus(), TIMEOUT);
+ expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
+ expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+ expectImeInvisible(TIMEOUT);
+ }
+ }
+
private static ImeSettings.Builder getFloatingImeSettings(@ColorInt int navigationBarColor) {
final ImeSettings.Builder builder = new ImeSettings.Builder();
builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index 0ca67f2..4bad587 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -1045,6 +1045,15 @@
}
}
+ @Test
+ public void testSimpleMuxNative() {
+ Assume.assumeTrue("TODO(b/146421018)",
+ !mMime.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
+ Assume.assumeTrue("TODO(b/146923287)",
+ !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+ assertTrue(nativeTestSimpleMux(mInpPath, mOutPath, mMime, selector));
+ }
+
/* Does MediaMuxer throw IllegalStateException on missing codec specific data when required.
* Check if relevant exception is thrown for AAC, AVC, HEVC, and MPEG4
* codecs that require CSD in MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4.
@@ -1077,14 +1086,161 @@
}
}
}
+ }
+
+ @LargeTest
+ @RunWith(Parameterized.class)
+ public static class TestAddEmptyTracks {
+ private final List<String> mimeListforTypeMp4 =
+ Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263,
+ MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC,
+ MediaFormat.MIMETYPE_AUDIO_AAC, MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC,
+ MediaFormat.MIMETYPE_TEXT_SUBRIP);
+ private final List<String> mimeListforTypeWebm =
+ Arrays.asList(MediaFormat.MIMETYPE_VIDEO_VP8, MediaFormat.MIMETYPE_VIDEO_VP9,
+ MediaFormat.MIMETYPE_AUDIO_VORBIS, MediaFormat.MIMETYPE_AUDIO_OPUS);
+ private final List<String> mimeListforType3gp =
+ Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263,
+ MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_AUDIO_AAC,
+ MediaFormat.MIMETYPE_AUDIO_AMR_NB, MediaFormat.MIMETYPE_AUDIO_AMR_WB);
+ private final List<String> mimeListforTypeOgg =
+ Arrays.asList(MediaFormat.MIMETYPE_AUDIO_OPUS);
+ private String mMime;
+ private String mOutPath;
+
+ public TestAddEmptyTracks(String mime) {
+ mMime = mime;
+ }
+
+ @Before
+ public void prologue() throws IOException {
+ mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath();
+ }
+
+ @After
+ public void epilogue() {
+ new File(mOutPath).delete();
+ }
+
+ private boolean isMimeContainerPairValid(int format) {
+ boolean result = false;
+ if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
+ result = mimeListforTypeMp4.contains(mMime);
+ else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM) {
+ return mimeListforTypeWebm.contains(mMime);
+ } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+ result = mimeListforType3gp.contains(mMime);
+ } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG) {
+ result = mimeListforTypeOgg.contains(mMime);
+ }
+ return result;
+ }
+
+ @Parameterized.Parameters(name = "{index}({0})")
+ public static Collection<Object[]> input() {
+ return Arrays.asList(new Object[][]{
+ // Video
+ {MediaFormat.MIMETYPE_VIDEO_H263},
+ {MediaFormat.MIMETYPE_VIDEO_AVC},
+ {MediaFormat.MIMETYPE_VIDEO_HEVC},
+ {MediaFormat.MIMETYPE_VIDEO_MPEG4},
+ {MediaFormat.MIMETYPE_VIDEO_VP8},
+ {MediaFormat.MIMETYPE_VIDEO_VP9},
+ // Audio
+ {MediaFormat.MIMETYPE_AUDIO_AAC},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_NB},
+ {MediaFormat.MIMETYPE_AUDIO_AMR_WB},
+ {MediaFormat.MIMETYPE_AUDIO_OPUS},
+ {MediaFormat.MIMETYPE_AUDIO_VORBIS},
+ // Metadata
+ {MediaFormat.MIMETYPE_TEXT_SUBRIP},
+ // Image
+ {MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC}
+ });
+ }
@Test
- public void testSimpleMuxNative() {
- Assume.assumeTrue("TODO(b/146421018)",
- !mMime.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
- Assume.assumeTrue("TODO(b/146923287)",
- !mMime.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
- assertTrue(nativeTestSimpleMux(mInpPath, mOutPath, mMime, selector));
+ public void testEmptyVideoTrack() {
+ for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
+ if (!mMime.startsWith("video/")) continue;
+ if (!isMimeContainerPairValid(format)) continue;
+ if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
+ try {
+ MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
+ MediaFormat mediaFormat = new MediaFormat();
+ mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
+ mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
+ mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+ mediaMuxer.addTrack(mediaFormat);
+ mediaMuxer.start();
+ mediaMuxer.stop();
+ mediaMuxer.release();
+ } catch (Exception e) {
+ fail("testEmptyVideoTrack : unexpected exception : " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testEmptyAudioTrack() {
+ for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
+ if (!mMime.startsWith("audio/")) continue;
+ if (!isMimeContainerPairValid(format)) continue;
+ if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
+ try {
+ MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
+ MediaFormat mediaFormat = new MediaFormat();
+ mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
+ mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 12000);
+ mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+ mediaMuxer.addTrack(mediaFormat);
+ mediaMuxer.start();
+ mediaMuxer.stop();
+ mediaMuxer.release();
+ } catch (Exception e) {
+ fail("testEmptyAudioTrack : unexpected exception : " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testEmptyMetaDataTrack() {
+ for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
+ if (!mMime.startsWith("application/")) continue;
+ if (!isMimeContainerPairValid(format)) continue;
+ try {
+ MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
+ MediaFormat mediaFormat = new MediaFormat();
+ mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
+ mediaMuxer.addTrack(mediaFormat);
+ mediaMuxer.start();
+ mediaMuxer.stop();
+ mediaMuxer.release();
+ } catch (Exception e) {
+ fail("testEmptyMetaDataTrack : unexpected exception : " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testEmptyImageTrack() {
+ for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
+ if (!mMime.startsWith("image/")) continue;
+ if (!isMimeContainerPairValid(format)) continue;
+ try {
+ MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
+ MediaFormat mediaFormat = new MediaFormat();
+ mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
+ mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
+ mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+ mediaMuxer.addTrack(mediaFormat);
+ mediaMuxer.start();
+ mediaMuxer.stop();
+ mediaMuxer.release();
+ } catch (Exception e) {
+ fail("testEmptyImageTrack : unexpected exception : " + e.getMessage());
+ }
+ }
}
}
}
diff --git a/tests/sensor/src/android/hardware/cts/SensorAdditionalInfoTest.java b/tests/sensor/src/android/hardware/cts/SensorAdditionalInfoTest.java
index df86fa6..00defeb 100644
--- a/tests/sensor/src/android/hardware/cts/SensorAdditionalInfoTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorAdditionalInfoTest.java
@@ -266,7 +266,7 @@
// Checks sensor placement data length and determinant of rotation matrix is 1.
private void verifySensorPlacementData(float[] m) {
- if(m.length != 12) {
+ if(m.length < 12) {
mIsSensorPlacementSizeValid = false;
return;
}
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
index 6503bdb..26f6b90 100644
--- a/tests/tests/appenumeration/AndroidTest.xml
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -26,7 +26,7 @@
<option name="force-queryable" value="false" />
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppEnumerationTestCases.apk" />
- <option name="test-file-name" value="CtsAppEnumerationForceQueryable.apk" />
+ <option name="test-file-name" value="CtsAppEnumerationForceQueryableNormalInstall.apk" />
<option name="test-file-name" value="CtsAppEnumerationFilters.apk" />
<option name="test-file-name" value="CtsAppEnumerationNoApi.apk" />
<option name="test-file-name" value="CtsAppEnumerationContactsActivityTarget.apk" />
@@ -61,6 +61,12 @@
<option name="test-file-name" value="CtsAppEnumerationWildcardBrowserActivitySource.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="force-queryable" value="true" />
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAppEnumerationForceQueryable.apk" />
+ </target_preparer>
+
<!-- Create place to store apks -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/cts/appenumeration" />
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
index 04ebc78..b3afe4b 100644
--- a/tests/tests/appenumeration/app/target/Android.bp
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -27,6 +27,20 @@
}
android_test_helper_app {
+ name: "CtsAppEnumerationForceQueryableNormalInstall",
+ manifest: "AndroidManifest-forceQueryable-normalInstall.xml",
+ defaults: ["cts_support_defaults"],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ sdk_version: "test_current",
+}
+
+android_test_helper_app {
name: "CtsAppEnumerationFilters",
manifest: "AndroidManifest-filters.xml",
defaults: ["cts_support_defaults"],
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
new file mode 100644
index 0000000..2918e37
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.appenumeration.forcequeryable.normalinstall">
+ <application android:forceQueryable="true">
+ <!-- This app will not be a system app and should be installed as a normal app (not
+ forceQueryable) to ensure it's not visible by default -->
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
index e6535b3..041d350 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.appenumeration.forcequeryable">
<application android:forceQueryable="true">
+ <!-- This app will not be a system app and so must be installed as forceQueryable by the
+ test framework -->
<uses-library android:name="android.test.runner" />
</application>
</manifest>
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index 6fcba54..d7c8dae 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -78,8 +78,13 @@
public static final String TARGET_SHARED_USER = PKG_BASE + "noapi.shareduid";
/** A package that exposes itself via various intent filters (activities, services, etc.) */
public static final String TARGET_FILTERS = PKG_BASE + "filters";
- /** A package that declares itself force queryable, making it visible to all other packages */
+ /** A package that declares itself force queryable, making it visible to all other packages.
+ * This is installed as forceQueryable as non-system apps cannot declare themselves as such. */
public static final String TARGET_FORCEQUERYABLE = PKG_BASE + "forcequeryable";
+ /** A package that declares itself force queryable, but is installed normally making it not
+ * visible to other packages */
+ public static final String TARGET_FORCEQUERYABLE_NORMAL =
+ PKG_BASE + "forcequeryable.normalinstall";
/** A package with no published API and so isn't queryable by anything but package name */
public static final String TARGET_NO_API = PKG_BASE + "noapi";
/** A package that offers an activity used for opening / editing file types */
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index d95970b..cfd12a2 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -66,6 +66,7 @@
import static android.appenumeration.cts.Constants.TARGET_FILTERS;
import static android.appenumeration.cts.Constants.TARGET_FILTERS_APK;
import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE;
+import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE_NORMAL;
import static android.appenumeration.cts.Constants.TARGET_NO_API;
import static android.appenumeration.cts.Constants.TARGET_SHARE;
import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
@@ -177,6 +178,15 @@
}
@Test
+ public void all_cannotSeeForceQueryableInstalledNormally() throws Exception {
+ assertNotVisible(QUERIES_NOTHING, TARGET_FORCEQUERYABLE_NORMAL);
+ assertNotVisible(QUERIES_ACTIVITY_ACTION, TARGET_FORCEQUERYABLE_NORMAL);
+ assertNotVisible(QUERIES_SERVICE_ACTION, TARGET_FORCEQUERYABLE_NORMAL);
+ assertNotVisible(QUERIES_PROVIDER_AUTH, TARGET_FORCEQUERYABLE_NORMAL);
+ assertNotVisible(QUERIES_PACKAGE, TARGET_FORCEQUERYABLE_NORMAL);
+ }
+
+ @Test
public void startExplicitly_canStartNonVisible() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
startExplicitIntentViaComponent(QUERIES_NOTHING, TARGET_FILTERS);
diff --git a/tests/tests/contactsprovider/Android.bp b/tests/tests/contactsprovider/Android.bp
index ac20675..67f5687 100644
--- a/tests/tests/contactsprovider/Android.bp
+++ b/tests/tests/contactsprovider/Android.bp
@@ -23,8 +23,13 @@
"truth-prebuilt",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+
+ // Include the GAL provider source code to pull in the constants.
+ "gal/src/**/*.java",
+ ],
sdk_version: "test_current",
- min_sdk_version: "21",
+ min_sdk_version: "30",
}
diff --git a/tests/tests/contactsprovider/AndroidManifest.xml b/tests/tests/contactsprovider/AndroidManifest.xml
index f81bb5e..23f01fe 100644
--- a/tests/tests/contactsprovider/AndroidManifest.xml
+++ b/tests/tests/contactsprovider/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.provider.cts.contacts">
- <uses-sdk android:targetSdkVersion="29" />
+ <uses-sdk android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
@@ -38,14 +38,6 @@
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/contactprovider_authenticator"/>
</service>
-
- <provider
- android:name=".DummyGalProvider"
- android:authorities="android.provider.cts.contacts.dgp"
- android:exported="true"
- android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
- <meta-data android:name="android.content.ContactDirectory" android:value="true" />
- </provider>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/contactsprovider/AndroidTest.xml b/tests/tests/contactsprovider/AndroidTest.xml
index 0d6562f..35c6b21 100644
--- a/tests/tests/contactsprovider/AndroidTest.xml
+++ b/tests/tests/contactsprovider/AndroidTest.xml
@@ -25,6 +25,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsContactsProviderTestCases.apk" />
+ <option name="test-file-name" value="CtsContactsProviderGalProvider.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/contactsprovider/gal/Android.bp b/tests/tests/contactsprovider/gal/Android.bp
new file mode 100644
index 0000000..4804a85
--- /dev/null
+++ b/tests/tests/contactsprovider/gal/Android.bp
@@ -0,0 +1,14 @@
+android_test_helper_app {
+ name: "CtsContactsProviderGalProvider",
+ defaults: ["cts_defaults"],
+
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+
+ srcs: ["src/**/*.java"],
+
+ min_sdk_version: "30",
+}
diff --git a/tests/tests/contactsprovider/gal/AndroidManifest.xml b/tests/tests/contactsprovider/gal/AndroidManifest.xml
new file mode 100644
index 0000000..e738ae3
--- /dev/null
+++ b/tests/tests/contactsprovider/gal/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.provider.cts.contacts.galprovider">
+
+ <uses-sdk android:targetSdkVersion="30" />
+
+ <application>
+ <provider
+ android:name=".CtsGalProvider"
+ android:authorities="android.provider.cts.contacts.gal"
+ android:exported="true"
+ android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
+ <meta-data android:name="android.content.ContactDirectory" android:value="true" />
+ </provider>
+ </application>
+</manifest>
+
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java b/tests/tests/contactsprovider/gal/src/android/provider/cts/contacts/galprovider/CtsGalProvider.java
similarity index 84%
rename from tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
rename to tests/tests/contactsprovider/gal/src/android/provider/cts/contacts/galprovider/CtsGalProvider.java
index 7609143..6806204 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
+++ b/tests/tests/contactsprovider/gal/src/android/provider/cts/contacts/galprovider/CtsGalProvider.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package android.provider.cts.contacts;
+package android.provider.cts.contacts.galprovider;
import android.content.ContentProvider;
import android.content.ContentValues;
@@ -26,7 +26,6 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
import android.text.TextUtils;
import android.util.Log;
@@ -36,15 +35,17 @@
/**
* GAL provider for CTS.
*/
-public class DummyGalProvider extends ContentProvider {
- private static final String TAG = "DummyGalProvider";
+public class CtsGalProvider extends ContentProvider {
+ private static final String TAG = "GalProvider";
- public static final String AUTHORITY = "android.provider.cts.contacts.dgp";
+ // The main CTS refers to it.
+ public static final String GAL_PACKAGE_NAME = "android.provider.cts.contacts.galprovider";
+ public static final String AUTHORITY = "android.provider.cts.contacts.gal";
- public static final String ACCOUNT_NAME = "dummygal";
- public static final String ACCOUNT_TYPE = StaticAccountAuthenticator.TYPE;
+ public static final String ACCOUNT_NAME = "ctsgal";
+ public static final String ACCOUNT_TYPE = "com.android.cts.contactsprovider";
- public static final String DISPLAY_NAME = "dummy-gal";
+ public static final String DISPLAY_NAME = "cts-gal";
public static final String ERROR_MESSAGE_KEY = "error";
public static final String QUERY_KEY = "query";
@@ -54,23 +55,14 @@
private static final int GAL_DIRECTORIES = 0;
private static final int GAL_FILTER = 1;
- private static final int GAL_CONTACT = 2;
- private static final int GAL_CONTACT_WITH_ID = 3;
- private static final int GAL_EMAIL_FILTER = 4;
- private static final int GAL_PHONE_FILTER = 5;
- private static final int GAL_PHONE_LOOKUP = 6;
+ private static final int SUB_PATH = 1;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, "directories", GAL_DIRECTORIES);
sURIMatcher.addURI(AUTHORITY, "contacts/filter/*", GAL_FILTER);
- // The following URIs are not supported by this class.
-// sURIMatcher.addURI(AUTHORITY, "contacts/lookup/*/entities", GAL_CONTACT);
-// sURIMatcher.addURI(AUTHORITY, "contacts/lookup/*/#/entities", GAL_CONTACT_WITH_ID);
-// sURIMatcher.addURI(AUTHORITY, "data/emails/filter/*", GAL_EMAIL_FILTER);
-// sURIMatcher.addURI(AUTHORITY, "data/phones/filter/*", GAL_PHONE_FILTER);
-// sURIMatcher.addURI(AUTHORITY, "phone_lookup/*", GAL_PHONE_LOOKUP);
+ sURIMatcher.addURI(AUTHORITY, "sub/*", SUB_PATH);
}
@Override
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/CallLogProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/CallLogProviderTest.java
new file mode 100644
index 0000000..8f55aa0
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/CallLogProviderTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.provider.cts.contacts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.test.InstrumentationTestCase;
+
+public class CallLogProviderTest extends InstrumentationTestCase {
+ private ContentResolver mContentResolver;
+ private ContentProviderClient mProvider;
+
+ private static final String TEST_NUMBER = "5551234";
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+ mProvider = mContentResolver.acquireContentProviderClient(CallLog.AUTHORITY);
+ }
+
+ public void testNoSubqueries() throws Exception {
+ // Add a single call just to make sure the call log has something inside
+ ContentValues values = new ContentValues();
+ values.put(CallLog.Calls.NUMBER, TEST_NUMBER);
+ values.put(CallLog.Calls.TYPE, Calls.OUTGOING_TYPE);
+ values.put(CallLog.Calls.DATE, Long.valueOf(0 /*start time*/));
+ values.put(CallLog.Calls.DURATION, Long.valueOf(5 /*call duration*/));
+
+ mContentResolver.insert(CallLog.Calls.CONTENT_URI, values);
+
+ // Attempt to do a query that contains a subquery -- this should fail since this test does
+ // not have READ_VOICEMAIL.
+ try {
+ Cursor c = mProvider.query(Calls.CONTENT_URI, null, CallLog.Calls.NUMBER + " = ?",
+ new String[]{TEST_NUMBER},
+ "date DESC LIMIT (SELECT count(*) + 1 FROM calls WHERE type = 4");
+ assertEquals(0, c.getCount());
+ } catch (IllegalArgumentException e) {
+ // expected/tolerated
+ }
+ }
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
index c3706d3..d800206 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
@@ -18,6 +18,7 @@
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
@@ -25,8 +26,9 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
-import android.provider.cts.contacts.DummyGalProvider;
+import android.provider.cts.contacts.galprovider.CtsGalProvider;
import android.test.AndroidTestCase;
+import android.util.Log;
import org.json.JSONObject;
@@ -36,6 +38,8 @@
* the check in there, so it won't create the account multiple times.
*/
public class ContactsContract_DirectoryTest extends AndroidTestCase {
+ private static final String TAG = "ContactsContract_DirectoryTest";
+
private ContentResolver mResolver;
private AccountManager mAccountManager;
private Account mAccount;
@@ -49,7 +53,7 @@
mResolver = getContext().getContentResolver();
mAccountManager = getContext().getSystemService(AccountManager.class);
- mAccount = new Account(DummyGalProvider.ACCOUNT_NAME, DummyGalProvider.ACCOUNT_TYPE);
+ mAccount = new Account(CtsGalProvider.ACCOUNT_NAME, CtsGalProvider.ACCOUNT_TYPE);
// The directory table is populated asynchronously. Wait for it...
waitForDirectorySetup();
@@ -75,19 +79,19 @@
while (SystemClock.elapsedRealtime() < timeout) {
try (Cursor c = getContext().getContentResolver().query(Directory.CONTENT_URI,
null, Directory.ACCOUNT_NAME + "=? and " + Directory.ACCOUNT_TYPE + "=?",
- new String[]{DummyGalProvider.ACCOUNT_NAME, DummyGalProvider.ACCOUNT_TYPE},
+ new String[]{CtsGalProvider.ACCOUNT_NAME, CtsGalProvider.ACCOUNT_TYPE},
null)) {
if (c.getCount() == 0) {
Thread.sleep(1000);
continue;
}
assertTrue(c.moveToPosition(0));
- assertEquals(getContext().getPackageName(), getString(c, Directory.PACKAGE_NAME));
- assertEquals(DummyGalProvider.AUTHORITY,
+ assertEquals(CtsGalProvider.GAL_PACKAGE_NAME, getString(c, Directory.PACKAGE_NAME));
+ assertEquals(CtsGalProvider.AUTHORITY,
getString(c, Directory.DIRECTORY_AUTHORITY));
- assertEquals(DummyGalProvider.DISPLAY_NAME, getString(c, Directory.DISPLAY_NAME));
- assertEquals(DummyGalProvider.ACCOUNT_NAME, getString(c, Directory.ACCOUNT_NAME));
- assertEquals(DummyGalProvider.ACCOUNT_TYPE, getString(c, Directory.ACCOUNT_TYPE));
+ assertEquals(CtsGalProvider.DISPLAY_NAME, getString(c, Directory.DISPLAY_NAME));
+ assertEquals(CtsGalProvider.ACCOUNT_NAME, getString(c, Directory.ACCOUNT_NAME));
+ assertEquals(CtsGalProvider.ACCOUNT_TYPE, getString(c, Directory.ACCOUNT_TYPE));
return c.getLong(c.getColumnIndex(Directory._ID));
}
}
@@ -127,14 +131,28 @@
// The result is stored in the display_name column.
final JSONObject result = new JSONObject(getString(c, Contacts.DISPLAY_NAME));
- if (result.has(DummyGalProvider.ERROR_MESSAGE_KEY)) {
- fail(result.getString(DummyGalProvider.ERROR_MESSAGE_KEY));
+ if (result.has(CtsGalProvider.ERROR_MESSAGE_KEY)) {
+ fail(result.getString(CtsGalProvider.ERROR_MESSAGE_KEY));
}
- assertEquals("12", result.getString(DummyGalProvider.LIMIT_KEY));
- assertEquals("[QUERY]", result.getString(DummyGalProvider.QUERY_KEY));
+ assertEquals("12", result.getString(CtsGalProvider.LIMIT_KEY));
+ assertEquals("[QUERY]", result.getString(CtsGalProvider.QUERY_KEY));
assertEquals(getContext().getPackageName(),
- result.getString(DummyGalProvider.CALLER_PACKAGE_NAME_KEY));
+ result.getString(CtsGalProvider.CALLER_PACKAGE_NAME_KEY));
+ }
+
+ // After getting any result from the gal provider, the package will become visible.
+ assertTrue("GAL provider should be visible here", isGalProviderVisible());
+ }
+
+ private boolean isGalProviderVisible() {
+ try {
+ String pkg = CtsGalProvider.GAL_PACKAGE_NAME;
+ int uid = getContext().getPackageManager().getPackageUid(pkg, 0);
+ Log.w(TAG, "UID of " + pkg + " = " + uid);
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
}
}
}
diff --git a/tests/tests/mediaparser/Android.bp b/tests/tests/mediaparser/Android.bp
index 6a13d24..13859a77 100644
--- a/tests/tests/mediaparser/Android.bp
+++ b/tests/tests/mediaparser/Android.bp
@@ -14,25 +14,33 @@
android_test {
name: "CtsMediaParserTestCases",
- defaults: ["cts_defaults"],
+ defaults: ["CtsMediaParserTestCasesDefaults", "cts_defaults"],
+ min_sdk_version: "29",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts",
+ ],
+}
+
+// App for host-side testing of the MediaParser integration with MediaMetrics.
+android_test_helper_app {
+ name: "CtsMediaParserTestCasesApp",
+ defaults: ["CtsMediaParserTestCasesDefaults"],
+}
+
+java_defaults {
+ name: "CtsMediaParserTestCasesDefaults",
+ srcs: ["src/**/*.java"],
static_libs: [
"ctstestrunner-axt",
"androidx.test.ext.junit",
"exoplayer2-extractor-test-utils",
"exoplayer2-extractor-tests-assets",
],
- srcs: ["src/**/*.java"],
- sdk_version: "test_current",
- min_sdk_version: "29",
libs: [
"android.test.base.stubs",
"android.test.runner.stubs",
],
-
- test_suites: [
- "cts",
- "vts10",
- "general-tests",
- "mts",
- ],
+ sdk_version: "test_current",
}
diff --git a/tests/tests/mediaparser/TEST_MAPPING b/tests/tests/mediaparser/TEST_MAPPING
index ec2d2e2..3d21914 100644
--- a/tests/tests/mediaparser/TEST_MAPPING
+++ b/tests/tests/mediaparser/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsMediaParserTestCases"
+ },
+ {
+ "name": "CtsMediaParserHostTestCases"
}
]
}
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
index 40ddad9..145ac99 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
@@ -673,42 +673,44 @@
mediaParser.setParameter(entry.getKey(), entry.getValue());
}
- mediaParser.advance(inputReader);
- if (expectedParserName != null) {
- assertThat(expectedParserName).isEqualTo(mediaParser.getParserName());
- // We are only checking that the extractor is the right one.
- mediaParser.release();
- return;
- }
+ try {
+ mediaParser.advance(inputReader);
+ if (expectedParserName != null) {
+ assertThat(expectedParserName).isEqualTo(mediaParser.getParserName());
+ // We are only checking that the extractor is the right one.
+ return;
+ }
- while (mediaParser.advance(inputReader)) {
- // Do nothing.
- }
+ while (mediaParser.advance(inputReader)) {
+ // Do nothing.
+ }
- // If the SeekMap is seekable, test seeking in the stream.
- MediaParser.SeekMap seekMap = outputConsumer.getSeekMap();
- assertThat(seekMap).isNotNull();
- if (seekMap.isSeekable()) {
- long durationUs = seekMap.getDurationMicros();
- for (int j = 0; j < 4; j++) {
- outputConsumer.clearTrackOutputs();
- long timeUs =
- durationUs == MediaParser.SeekMap.UNKNOWN_DURATION
- ? 0
- : (durationUs * j) / 3;
- MediaParser.SeekPoint seekPoint = seekMap.getSeekPoints(timeUs).first;
- inputReader.reset();
- inputReader.setPosition((int) seekPoint.position);
- mediaParser.seek(seekPoint);
- while (mediaParser.advance(inputReader)) {
- // Do nothing.
- }
- if (durationUs == MediaParser.SeekMap.UNKNOWN_DURATION) {
- break;
+ // If the SeekMap is seekable, test seeking in the stream.
+ MediaParser.SeekMap seekMap = outputConsumer.getSeekMap();
+ assertThat(seekMap).isNotNull();
+ if (seekMap.isSeekable()) {
+ long durationUs = seekMap.getDurationMicros();
+ for (int j = 0; j < 4; j++) {
+ outputConsumer.clearTrackOutputs();
+ long timeUs =
+ durationUs == MediaParser.SeekMap.UNKNOWN_DURATION
+ ? 0
+ : (durationUs * j) / 3;
+ MediaParser.SeekPoint seekPoint = seekMap.getSeekPoints(timeUs).first;
+ inputReader.reset();
+ inputReader.setPosition((int) seekPoint.position);
+ mediaParser.seek(seekPoint);
+ while (mediaParser.advance(inputReader)) {
+ // Do nothing.
+ }
+ if (durationUs == MediaParser.SeekMap.UNKNOWN_DURATION) {
+ break;
+ }
}
}
+ } finally {
+ mediaParser.release();
}
- mediaParser.release();
}
private static MockMediaParserInputReader getInputReader(String assetPath) throws IOException {
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index 88e6b68..9f414b6 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -48,6 +48,53 @@
const StreamBuilderHelper::Parameters& actual() const { return mHelper->actual(); }
int32_t framesPerBurst() const { return mHelper->framesPerBurst(); }
+ // This checks for expected behavior after a stream has been released.
+ void checkCallsAfterRelease() {
+ // We expect these not to crash.
+ AAudioStream_setBufferSizeInFrames(stream(), 0);
+ AAudioStream_setBufferSizeInFrames(stream(), 99999999);
+
+ // We should NOT be able to start or change a stream after it has been released.
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE,
+ AAudioStream_requestStart(stream()));
+ EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(stream()));
+ // Pause is only implemented for OUTPUT.
+ if (AAudioStream_getDirection(stream()) == AAUDIO_DIRECTION_OUTPUT) {
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE,
+ AAudioStream_requestPause(stream()));
+ }
+ EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(stream()));
+ EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE,
+ AAudioStream_requestStop(stream()));
+ EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, AAudioStream_getState(stream()));
+
+ // Do these return positive integers?
+ // Frames read or written may be zero if the stream has not had time to advance.
+ EXPECT_GE(AAudioStream_getFramesRead(stream()), 0);
+ EXPECT_GE(AAudioStream_getFramesWritten(stream()), 0);
+ EXPECT_GT(AAudioStream_getFramesPerBurst(stream()), 0);
+ EXPECT_GE(AAudioStream_getXRunCount(stream()), 0);
+ EXPECT_GT(AAudioStream_getBufferCapacityInFrames(stream()), 0);
+ EXPECT_GT(AAudioStream_getBufferSizeInFrames(stream()), 0);
+
+ int64_t timestampFrames = 0;
+ int64_t timestampNanos = 0;
+ aaudio_result_t result = AAudioStream_getTimestamp(stream(), CLOCK_MONOTONIC,
+ ×tampFrames, ×tampNanos);
+ EXPECT_TRUE(result == AAUDIO_ERROR_INVALID_STATE
+ || result == AAUDIO_ERROR_UNIMPLEMENTED
+ || result == AAUDIO_OK
+ );
+
+ // Verify Closing State. Does this crash?
+ aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(stream(),
+ AAUDIO_STREAM_STATE_UNKNOWN,
+ &state,
+ 500 * NANOS_PER_MILLISECOND));
+ EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, state);
+ }
+
std::unique_ptr<T> mHelper;
bool mSetupSuccessful = false;
std::unique_ptr<int16_t[]> mData;
@@ -171,6 +218,9 @@
aaudio_stream_state_t state = AAudioStream_getState(stream());
EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, state);
}
+
+ checkCallsAfterRelease();
+
}
INSTANTIATE_TEST_CASE_P(SPM, AAudioInputStreamTest,
@@ -376,11 +426,16 @@
if (!mSetupSuccessful) return;
mHelper->startStream();
- aaudio_result_t result = AAudioStream_write(
- stream(), &mData[0], framesPerBurst(),
- DEFAULT_READ_TIMEOUT);
- ASSERT_GT(result, 0);
+ // Write a few times so the device has time to read some of the data
+ // and maybe advance the framesRead.
+ for (int i = 0; i < 3; i++) {
+ aaudio_result_t result = AAudioStream_write(
+ stream(), &mData[0], framesPerBurst(),
+ DEFAULT_READ_TIMEOUT);
+ ASSERT_GT(result, 0);
+ }
mHelper->stopStream();
+ EXPECT_GE(AAudioStream_getFramesRead(stream()), 0);
// It should be safe to release multiple times.
for (int i = 0; i < 3; i++) {
@@ -388,6 +443,9 @@
aaudio_stream_state_t state = AAudioStream_getState(stream());
EXPECT_EQ(AAUDIO_STREAM_STATE_CLOSING, state);
}
+
+ checkCallsAfterRelease();
+
}
// Note that the test for EXCLUSIVE sharing mode may fail gracefully if
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
new file mode 100644
index 0000000..a5e2fd3
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 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.
+
+android_test_helper_app {
+ name: "CtsSelfUninstallingTestApp",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.core_core",
+ ],
+
+ // tag this module as a cts test artifact
+ test_suites: [
+ "arcts",
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+}
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..87dc715
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.packageinstaller.selfuninstalling.cts" >
+
+ <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+
+ <application android:label="Self Uninstalling Test App">
+ <activity android:name=".SelfUninstallActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml
new file mode 100644
index 0000000..ac0fb40
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/res/layout/self_uninstalling_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Pin me!" />
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java
new file mode 100644
index 0000000..df5b1d4
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/src/android/packageinstaller/selfuninstalling/cts/SelfUninstallActivity.java
@@ -0,0 +1,33 @@
+package android.packageinstaller.selfuninstalling.cts;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.Nullable;
+
+public class SelfUninstallActivity extends Activity {
+
+ private static final String ACTION_SELF_UNINSTALL =
+ "android.packageinstaller.selfuninstalling.cts.action.SELF_UNINSTALL";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.self_uninstalling_activity);
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent i = new Intent(Intent.ACTION_UNINSTALL_PACKAGE).setData(
+ Uri.fromParts("package", getPackageName(), null));
+ startActivity(i);
+ }
+ }, new IntentFilter(ACTION_SELF_UNINSTALL));
+ }
+}
diff --git a/tests/tests/packageinstaller/uninstall/Android.bp b/tests/tests/packageinstaller/uninstall/Android.bp
index 9f61f87..50d1a52 100644
--- a/tests/tests/packageinstaller/uninstall/Android.bp
+++ b/tests/tests/packageinstaller/uninstall/Android.bp
@@ -20,6 +20,7 @@
"androidx.test.rules",
"compatibility-device-util-axt",
"platform-test-annotations",
+ "cts-wm-util",
],
resource_dirs: ["res"],
srcs: ["src/**/*.java"],
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
index 9fc0a88..dd98214 100644
--- a/tests/tests/packageinstaller/uninstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -31,4 +31,14 @@
<option name="package" value="android.packageinstaller.uninstall.cts" />
<option name="runtime-hint" value="1m" />
</test>
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/uninstall" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsSelfUninstallingTestApp.apk->/data/local/tmp/cts/uninstall/CtsSelfUninstallingTestApp.apk" />
+ </target_preparer>
</configuration>
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
new file mode 100644
index 0000000..84c2696
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 Google Inc.
+ *
+ * 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.packageinstaller.uninstall.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.server.wm.WindowManagerStateHelper;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class UninstallPinnedTest {
+
+ private static final String APK =
+ "/data/local/tmp/cts/uninstall/CtsSelfUninstallingTestApp.apk";
+ private static final String TEST_PKG_NAME = "android.packageinstaller.selfuninstalling.cts";
+ private static final String TEST_ACTIVITY_NAME = TEST_PKG_NAME + ".SelfUninstallActivity";
+ private static final String ACTION_SELF_UNINSTALL =
+ "android.packageinstaller.selfuninstalling.cts.action.SELF_UNINSTALL";
+ private static final ComponentName COMPONENT = new ComponentName(TEST_PKG_NAME, TEST_ACTIVITY_NAME);
+ public static final String CALLBACK_ACTION =
+ "android.packageinstaller.uninstall.cts.action.UNINSTALL_PINNED_CALLBACK";
+
+ private WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+ private Context mContext;
+ private UiDevice mUiDevice;
+ private ActivityTaskManager mActivityTaskManager;
+
+ @Before
+ public void setup() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+
+ // Unblock UI
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ if (!mUiDevice.isScreenOn()) {
+ mUiDevice.wakeUp();
+ }
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ AppOpsUtils.reset(mContext.getPackageName());
+
+ runShellCommand("pm install -r --force-queryable " + APK);
+
+ Intent i = new Intent()
+ .setComponent(COMPONENT)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(i);
+
+ pinActivity(COMPONENT);
+ }
+
+ @Test
+ public void testAppCantUninstallItself() throws Exception {
+ mUiDevice.waitForIdle();
+ eventually(() -> {
+ mContext.sendBroadcast(new Intent(ACTION_SELF_UNINSTALL));
+ waitFindObject(By.text("OK")).click();
+ }, 60000);
+
+ mUiDevice.waitForIdle();
+
+ Thread.sleep(5000);
+
+ assertTrue("Package was uninstalled.", isInstalled());
+ }
+
+ @Test
+ public void testCantUninstallAppDirectly() {
+ CompletableFuture<Integer> statusFuture = new CompletableFuture<>();
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ statusFuture.complete(
+ intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MAX_VALUE));
+ }
+ }, new IntentFilter(CALLBACK_ACTION));
+
+ runWithShellPermissionIdentity(() -> {
+ mContext.getPackageManager().getPackageInstaller().uninstall(TEST_PKG_NAME,
+ PendingIntent.getBroadcast(mContext, 1,
+ new Intent(CALLBACK_ACTION),
+ 0).getIntentSender());
+ });
+
+ int status = statusFuture.join();
+ assertEquals("Wrong code received", PackageInstaller.STATUS_FAILURE_BLOCKED, status);
+ assertTrue("Package was uninstalled.", isInstalled());
+ }
+
+ @Test
+ public void testCantUninstallWithShell() throws Exception {
+ mUiDevice.executeShellCommand("pm uninstall " + TEST_PKG_NAME);
+ assertTrue("Package was uninstalled.", isInstalled());
+ }
+
+ @After
+ public void unpinAndUninstall() throws IOException {
+ runWithShellPermissionIdentity(() -> mActivityTaskManager.stopSystemLockTaskMode());
+ mUiDevice.executeShellCommand("pm uninstall " + TEST_PKG_NAME);
+ }
+
+ private void pinActivity(ComponentName component) {
+ mWmState.computeState();
+
+ int stackId = mWmState.getStackIdByActivity(component);
+
+ runWithShellPermissionIdentity(() -> {
+ mActivityTaskManager.startSystemLockTaskMode(
+ stackId);
+ });
+ }
+
+ private boolean isInstalled() {
+ try {
+ mContext.getPackageManager().getPackageInfo(TEST_PKG_NAME, 0);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 451360d..4acb83f 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -4263,6 +4263,10 @@
<permission android:name="android.permission.WRITE_DREAM_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows applications to read whether ambient display is suppressed. -->
+ <permission android:name="android.permission.READ_DREAM_SUPPRESSION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allow an application to read and write the cache partition.
@hide -->
<permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
diff --git a/tests/tests/permission4/Android.bp b/tests/tests/permission4/Android.bp
new file mode 100644
index 0000000..ad1658d
--- /dev/null
+++ b/tests/tests/permission4/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test {
+ name: "CtsPermission4TestCases",
+ sdk_version: "system_current",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ ],
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ "mts",
+ ],
+}
diff --git a/tests/tests/permission4/AndroidManifest.xml b/tests/tests/permission4/AndroidManifest.xml
new file mode 100644
index 0000000..d4cc71a
--- /dev/null
+++ b/tests/tests/permission4/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permission4.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+ <application>
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name=".StartForFutureActivity" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permission4.cts"
+ android:label="CTS UI tests for permissions">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/tests/tests/permission4/AndroidTest.xml b/tests/tests/permission4/AndroidTest.xml
new file mode 100644
index 0000000..71353aa
--- /dev/null
+++ b/tests/tests/permission4/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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 CTS Permission4 test cases">
+
+ <option name="test-suite-tag" value="cts" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermission4TestCases.apk" />
+ <option name="test-file-name" value="CtsAppThatAccessesMicAndCameraPermission.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permission4.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
new file mode 100644
index 0000000..508e44c
--- /dev/null
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test_helper_app {
+ name: "CtsAppThatAccessesMicAndCameraPermission",
+ defaults: ["cts_defaults"],
+ sdk_version: "system_current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+ ],
+
+ srcs: [
+ "src/**/*.kt"
+ ],
+}
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/AndroidManifest.xml b/tests/tests/permission4/AppThatAccessesCameraAndMic/AndroidManifest.xml
new file mode 100644
index 0000000..938b5b5
--- /dev/null
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.permission4.cts.appthataccessescameraandmic"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+ <uses-permission android:name="android.permission.CAMERA"/>
+
+ <application android:label="CtsCameraMicAccess">
+ <activity android:name=".AccessCameraOrMicActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="test.action.USE_CAMERA_OR_MIC" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt b/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
new file mode 100644
index 0000000..2be6926
--- /dev/null
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/src/android/permission4/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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.permission4.cts.appthataccessescameraandmic
+
+import android.app.Activity
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.media.AudioFormat.CHANNEL_IN_MONO
+import android.media.AudioFormat.ENCODING_PCM_16BIT
+import android.media.AudioRecord
+import android.media.MediaRecorder.AudioSource.MIC
+import androidx.annotation.NonNull
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val USE_CAMERA = "use_camera"
+private const val USE_MICROPHONE = "use_microphone"
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * Activity which will, depending on the extra passed in the intent, use the camera, the microphone,
+ * or both.
+ */
+class AccessCameraOrMicActivity : Activity() {
+ private lateinit var cameraId: String
+ private var cameraDevice: CameraDevice? = null
+ private var recorder: AudioRecord? = null
+ private var cameraFinished = false
+ private var runCamera = false
+ private var micFinished = false
+ private var runMic = false
+
+ override fun onStart() {
+ super.onStart()
+ runCamera = intent.getBooleanExtra(USE_CAMERA, false)
+ runMic = intent.getBooleanExtra(USE_MICROPHONE, false)
+
+ if (runMic) {
+ useMic()
+ }
+
+ if (runCamera) {
+ useCamera()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ cameraDevice?.close()
+ recorder?.stop()
+ finish()
+ }
+
+ private val stateCallback = object : CameraDevice.StateCallback() {
+ override fun onOpened(@NonNull camDevice: CameraDevice) {
+ cameraDevice = camDevice
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ cameraFinished = true
+ if (!runMic || micFinished) {
+ finish()
+ }
+ }
+ }
+
+ override fun onDisconnected(@NonNull camDevice: CameraDevice) {
+ camDevice.close()
+ throw RuntimeException("Camera was disconnected")
+ }
+
+ override fun onError(@NonNull camDevice: CameraDevice, error: Int) {
+ camDevice.close()
+ throw RuntimeException("Camera error")
+ }
+ }
+
+ @Throws(CameraAccessException::class)
+ private fun useCamera() {
+ val manager = getSystemService(CameraManager::class.java)!!
+ cameraId = manager.cameraIdList[0]
+ manager.openCamera(cameraId, mainExecutor, stateCallback)
+ }
+
+ private fun useMic() {
+ val minSize =
+ AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT)
+ recorder = AudioRecord(MIC, SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, minSize)
+ recorder?.startRecording()
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ micFinished = true
+ if (!runCamera || cameraFinished) {
+ finish()
+ }
+ }
+ }
+}
diff --git a/tests/tests/permission4/OWNERS b/tests/tests/permission4/OWNERS
new file mode 100644
index 0000000..d4d6a95
--- /dev/null
+++ b/tests/tests/permission4/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 137825
+svetoslavganov@google.com
+moltmann@google.com
+zhanghai@google.com
+eugenesusla@google.com
+evanseverson@google.com
+ntmyren@google.com
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
new file mode 100644
index 0000000..0f36f5a
--- /dev/null
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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.permission4.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.app.compat.CompatChanges
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraManager
+import android.os.Process
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiSelector
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+
+private const val APP_LABEL = "CtsCameraMicAccess"
+private const val USE_CAMERA = "use_camera"
+private const val USE_MICROPHONE = "use_microphone"
+private const val INTENT_ACTION = "test.action.USE_CAMERA_OR_MIC"
+private const val PRIVACY_CHIP_ID = "com.android.systemui:id/privacy_chip"
+private const val INDICATORS_FLAG = "camera_mic_icons_enabled"
+private const val PERMISSION_INDICATORS_NOT_PRESENT = 162547999L
+private const val IDLE_TIMEOUT_MILLIS: Long = 1000
+private const val UNEXPECTED_TIMEOUT_MILLIS = 1000
+private const val TIMEOUT_MILLIS: Long = 20000
+
+class CameraMicIndicatorsPermissionTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ private val uiAutomation: UiAutomation = instrumentation.uiAutomation
+ private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+ private val packageManager: PackageManager = context.packageManager
+
+ private var wasEnabled = false
+ private val micLabel = packageManager.getPermissionGroupInfo(
+ Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ private val cameraLabel = packageManager.getPermissionGroupInfo(
+ Manifest.permission_group.CAMERA, 0).loadLabel(packageManager).toString()
+
+ private var screenTimeoutBeforeTest: Long = 0L
+
+ @Before
+ fun setUp() {
+ runWithShellPermissionIdentity {
+ screenTimeoutBeforeTest = Settings.System.getLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT
+ )
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 1800000L
+ )
+ }
+
+ uiDevice.wakeUp()
+ runShellCommand(instrumentation, "wm dismiss-keyguard")
+
+ uiDevice.findObject(By.text("Close"))?.click()
+ wasEnabled = setIndicatorsEnabledStateIfNeeded(true)
+ // If the change Id is not present, then isChangeEnabled will return true. To bypass this,
+ // the change is set to "false" if present.
+ assumeFalse("feature not present on this device", callWithShellPermissionIdentity {
+ CompatChanges.isChangeEnabled(PERMISSION_INDICATORS_NOT_PRESENT, Process.SYSTEM_UID)
+ })
+ }
+
+ private fun setIndicatorsEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+ var currentlyEnabled = false
+ runWithShellPermissionIdentity {
+ currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ INDICATORS_FLAG, false)
+ if (currentlyEnabled != shouldBeEnabled) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG,
+ shouldBeEnabled.toString(), false)
+ }
+ }
+ return currentlyEnabled
+ }
+
+ @After
+ fun tearDown() {
+ if (!wasEnabled) {
+ setIndicatorsEnabledStateIfNeeded(false)
+ }
+ runWithShellPermissionIdentity {
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ screenTimeoutBeforeTest
+ )
+ }
+
+ pressHome()
+ }
+
+ private fun openApp(useMic: Boolean, useCamera: Boolean) {
+ context.startActivity(Intent(INTENT_ACTION).apply {
+ putExtra(USE_CAMERA, useCamera)
+ putExtra(USE_MICROPHONE, useMic)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ })
+ }
+
+ @Test
+ fun testCameraIndicator() {
+ val manager = context.getSystemService(CameraManager::class.java)!!
+ assumeTrue(manager.cameraIdList.isNotEmpty())
+ testCameraAndMicIndicator(useMic = false, useCamera = true)
+ }
+
+ @Test
+ fun testMicIndicator() {
+ testCameraAndMicIndicator(useMic = true, useCamera = false)
+ }
+
+ private fun testCameraAndMicIndicator(useMic: Boolean, useCamera: Boolean) {
+ openApp(useMic, useCamera)
+ eventually {
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ }
+ uiDevice.openNotification()
+ // Ensure the privacy chip is present
+ eventually {
+ val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
+ assertTrue("view with id $PRIVACY_CHIP_ID not found", privacyChip.exists())
+ privacyChip.click()
+ }
+ eventually {
+ if (useMic) {
+ val appView = uiDevice.findObject(UiSelector().textContains(micLabel))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ }
+ if (useCamera) {
+ val appView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ }
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ }
+ pressBack()
+ }
+
+ private fun pressBack() {
+ uiDevice.pressBack()
+ waitForIdle()
+ }
+
+ private fun pressHome() {
+ uiDevice.pressHome()
+ waitForIdle()
+ }
+
+ private fun waitForIdle() =
+ uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+}
\ No newline at end of file
diff --git a/tests/tests/telecom/src/android/telecom/cts/MissedCallTest.java b/tests/tests/telecom/src/android/telecom/cts/MissedCallTest.java
index ee9359c..5043e15 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MissedCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MissedCallTest.java
@@ -17,6 +17,7 @@
package android.telecom.cts;
import android.content.Intent;
+import android.os.Process;
import android.telecom.Call;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -29,6 +30,8 @@
TestUtils.InvokeCounter mShowMissedCallNotificationIntentCounter =
new TestUtils.InvokeCounter("ShowMissedCallNotificationIntent");
+ private static final String CMD_DEVICE_IDLE_TEMP_EXEMPTIONS = "cmd deviceidle tempwhitelist";
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -71,6 +74,15 @@
connection.setDisconnected(new DisconnectCause(DisconnectCause.MISSED));
connection.destroy();
mShowMissedCallNotificationIntentCounter.waitForCount(1);
+ assertTrue("After missing a call, if the default dialer is handling the missed call "
+ + "notification, then it must be in the temporary power exemption list.",
+ isOnTemporaryPowerExemption());
}
+ private boolean isOnTemporaryPowerExemption() throws Exception {
+ String exemptions = TestUtils.executeShellCommand(
+ getInstrumentation(), CMD_DEVICE_IDLE_TEMP_EXEMPTIONS);
+ // Just check that this process's UID is in the result.
+ return exemptions.contains(String.valueOf(Process.myUid()));
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellIdentityTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellIdentityTest.java
index b87d594..1112b28 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellIdentityTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellIdentityTest.java
@@ -22,6 +22,7 @@
import android.telephony.CellIdentity;
import android.telephony.CellIdentityCdma;
import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
import android.telephony.CellIdentityNr;
import android.telephony.CellIdentityTdscdma;
import android.telephony.CellIdentityWcdma;
@@ -97,14 +98,48 @@
}
@Test
- public void testCellIdentityNr_asCellLocation() {
- CellIdentity cellIdentity =
- new CellIdentityNr(123, 456, 789, null, null, null, 0, null, null, EMPTY_SET);
+ public void testCellIdentityLte_asCellLocation() {
+ int tac = 1;
+ int ci = 2;
+ CellIdentity cellIdentity = new CellIdentityLte(123, 456, ci, 7, tac);
CellLocation cellLocation = cellIdentity.asCellLocation();
GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
- assertEquals(new GsmCellLocation(), gsmCellLocation);
+ assertEquals(tac, gsmCellLocation.getLac());
+ assertEquals(ci, gsmCellLocation.getCid());
+ // psc is not supported in LTE so always 0
+ assertEquals(0, gsmCellLocation.getPsc());
+ }
+
+ @Test
+ public void testCellIdentityLte_unavailable_asCellLocation() {
+ CellIdentity cellIdentity = new CellIdentityLte();
+
+ CellLocation cellLocation = cellIdentity.asCellLocation();
+
+ GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
+ // -1 for unintialized lac and cid
+ assertEquals(-1, gsmCellLocation.getLac());
+ assertEquals(-1, gsmCellLocation.getCid());
+ // psc is not supported in LTE so always 0
+ assertEquals(0, gsmCellLocation.getPsc());
+ }
+
+ @Test
+ public void testCellIdentityNr_asCellLocation() {
+ int tac = 1;
+ CellIdentity cellIdentity =
+ new CellIdentityNr(123, tac, 789, null, null, null, 321, null, null, EMPTY_SET);
+
+ CellLocation cellLocation = cellIdentity.asCellLocation();
+
+ GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
+ assertEquals(tac, gsmCellLocation.getLac());
+ // NR cid is 36 bits and can't fit into the 32-bit cid in GsmCellLocation, so always -1.
+ assertEquals(-1, gsmCellLocation.getCid());
+ // psc is not supported in NR so always 0, same as in LTE
+ assertEquals(0, gsmCellLocation.getPsc());
}
@Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 0a5a374..c6f1aa6 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -331,6 +331,9 @@
assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
TextUtils.isEmpty(mDestAddr));
+ // disable suppressing blocking.
+ TelephonyUtils.endBlockSuppression(getInstrumentation());
+
String mccmnc = mTelephonyManager.getSimOperator();
// Setting default SMS App is needed to be able to block numbers.
setDefaultSmsApp(true);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
index 2bd8bb8..f389098 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
@@ -33,6 +33,8 @@
private static final String COMMAND_REMOVE_TEST_EMERGENCY_NUMBER =
"cmd phone emergency-number-test-mode -r ";
+ private static final String COMMAND_END_BLOCK_SUPPRESSION = "cmd phone end-block-suppression";
+
public static void addTestEmergencyNumber(Instrumentation instr, String testNumber)
throws Exception {
executeShellCommand(instr, COMMAND_ADD_TEST_EMERGENCY_NUMBER + testNumber);
@@ -43,6 +45,10 @@
executeShellCommand(instr, COMMAND_REMOVE_TEST_EMERGENCY_NUMBER + testNumber);
}
+ public static void endBlockSuppression(Instrumentation instr) throws Exception {
+ executeShellCommand(instr, COMMAND_END_BLOCK_SUPPRESSION);
+ }
+
public static boolean isSkt(TelephonyManager telephonyManager) {
return isOperator(telephonyManager, "45005");
}