CTS verifier: "inline reply must reset shortcut manager rate-limiting"
Bug 30916315
Change-Id: Ia91135a1230de27e5bd077e5467bf9373f1cb201
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 824ab67..6f29335 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1246,6 +1246,18 @@
</intent-filter>
</service>
+ <activity android:name=".notifications.ShortcutThrottlingResetActivity"
+ android:label="@string/shortcut_reset_test"
+ android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+ <meta-data android:name="test_excluded_features"
+ android:value="android.hardware.type.watch:android.software.leanback" />
+ </activity>
+
<activity android:name=".vr.VrListenerVerifierActivity"
android:label="@string/vr_tests">
<intent-filter>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 2406b84..d6b0f47 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1204,6 +1204,16 @@
<string name="package_priority_default">Find \"%s\" in the \"Notifications\" settings panel, and disallow it to Override Do Not Disturb.</string>
<string name="package_priority_user_order">Check that ranker respects user priorities.</string>
+ <string name="shortcut_reset_test">Shortcut Reset Rate-limiting Test</string>
+ <string name="shortcut_reset_info">This test checks that when an inline-reply happens, ShortcutManager\'s rate-limiting
+ is reset.</string>
+ <string name="shortcut_reset_bot">Verifying that the CTS Robot helper package is installed.</string>
+ <string name="shortcut_reset_start">Showing the notification.</string>
+ <string name="shortcut_reset_prompt_inline_reply">Open the notification shade,
+ find the \"Shortcut Reset Rate-limiting Test\" notification, type something in the inline-reply field and
+ press the send button.</string>
+ <string name="shortcut_reset_check_result">Check ShortcutManager rate-limit has been reset.</string>
+
<string name="attention_test">Notification Attention Management Test</string>
<string name="attention_info">This test checks that the NotificationManagerService is
respecting user preferences about notification ranking and filtering.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
index 5870981..b40ecc6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -39,7 +39,7 @@
private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
private static final String EXTRA_ID = "ID";
private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
- private static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
+ static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
private CharSequence mAppLabel;
@Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java
new file mode 100644
index 0000000..313ebfa
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ShortcutThrottlingResetActivity.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.cts.verifier.R;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test to make sure, when an inline reply happens, the shortcut manager rate-limiting must
+ * be reset.
+ *
+ * We use the "BOT" apk here, because rate-limiting will be reset when an app shows an activity
+ * too -- so as long as this (or any) test activity is shown, CTS verifier won't be rate-limited.
+ */
+public class ShortcutThrottlingResetActivity extends InteractiveVerifierActivity {
+ private static final String TAG = "ShortcutThrottlingReset";
+
+ private static final String NOTIFICATION_BOT_PACKAGE
+ = PackagePriorityVerifierActivity.NOTIFICATION_BOT_PACKAGE;
+
+ private static final String ACTION_RESET_SETUP_NOTIFICATION =
+ "com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION";
+
+ private static final String EXTRA_NOTIFICATION_TITLE = "EXTRA_NOTIFICATION_TITLE";
+ private static final String EXTRA_RESET_REPLY_PACKAGE = "EXTRA_RESET_REPLY_PACKAGE";
+ private static final String EXTRA_RESET_REPLY_ACTION = "EXTRA_RESET_REPLY_ACTION";
+ private static final String EXTRA_RESET_REPLY_ERROR = "EXTRA_RESET_REPLY_ERROR";
+
+ private static final String SUCCESS = "**SUCCESS**";
+
+ private String mReplyAction;
+
+ private final AtomicReference<Intent> mReplyIntent = new AtomicReference<>(null);
+
+ @Override
+ int getTitleResource() {
+ return R.string.shortcut_reset_test;
+ }
+
+ @Override
+ int getInstructionsResource() {
+ return R.string.shortcut_reset_info;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ // Generate an unique reply action and register the reply receiver.
+ mReplyAction = "reply_" + new SecureRandom().nextLong();
+ final IntentFilter replyFilter = new IntentFilter(mReplyAction);
+ registerReceiver(mReplyReceiver, replyFilter);
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mReplyReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ protected List<InteractiveTestCase> createTestItems() {
+ List<InteractiveTestCase> tests = new ArrayList<>();
+ tests.add(new CheckForBot());
+ tests.add(new SetupNotification());
+ tests.add(new WaitForTestReply());
+ tests.add(new CheckResult());
+ return tests;
+ }
+
+
+ private final BroadcastReceiver mReplyReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received reply from robot helper: " + intent);
+ mReplyIntent.set(intent);
+ }
+ };
+
+
+ /** Make sure the helper package is installed. */
+ protected class CheckForBot extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.shortcut_reset_bot);
+ }
+
+ @Override
+ void test() {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageInfo(NOTIFICATION_BOT_PACKAGE, 0);
+ status = PASS;
+ } catch (PackageManager.NameNotFoundException e) {
+ status = FAIL;
+ logFail("You must install the CTS Robot helper, aka " + NOTIFICATION_BOT_PACKAGE);
+ }
+ next();
+ }
+ }
+
+ /**
+ * Request the bot apk to show the notification.
+ */
+ protected class SetupNotification extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.shortcut_reset_start);
+ }
+
+ @Override
+ void test() {
+ final Intent intent = new Intent(ACTION_RESET_SETUP_NOTIFICATION);
+ intent.setPackage(NOTIFICATION_BOT_PACKAGE);
+
+ intent.putExtra(EXTRA_NOTIFICATION_TITLE, getResources().getString(getTitleResource()));
+
+ intent.putExtra(EXTRA_RESET_REPLY_PACKAGE, getPackageName());
+ intent.putExtra(EXTRA_RESET_REPLY_ACTION, mReplyAction);
+
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ sendBroadcast(intent);
+ status = PASS;
+ next();
+ }
+ }
+
+ /**
+ * Let the human tester do an inline reply, and wait for the reply broadcast from the bot apk.
+ */
+ protected class WaitForTestReply extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.shortcut_reset_prompt_inline_reply);
+ }
+
+ @Override
+ void test() {
+ final Intent replyIntent = mReplyIntent.get();
+ if (replyIntent == null) {
+ // Reply not received yet.
+ status = RETEST;
+ delay();
+ return;
+ }
+ status = PASS;
+ next();
+ }
+ }
+
+ /**
+ * Check the reply from the bot apk.
+ */
+ protected class CheckResult extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.shortcut_reset_check_result);
+ }
+
+ @Override
+ void test() {
+ final Intent replyIntent = mReplyIntent.get();
+ if (replyIntent == null) {
+ logFail("Internal error, replyIntent shouldn't be null here.");
+ status = FAIL;
+ return;
+ }
+ final String error = replyIntent.getStringExtra(EXTRA_RESET_REPLY_ERROR);
+ if (SUCCESS.equals(error)) {
+ status = PASS;
+ next();
+ return;
+ }
+ logFail("Test failed. Error message=" + error);
+ status = FAIL;
+ next();
+ }
+ }
+}
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
index b63791f..0388cbc 100644
--- a/apps/NotificationBot/AndroidManifest.xml
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -39,10 +39,10 @@
<intent-filter>
<action android:name="com.android.cts.robot.ACTION_POST" />
<action android:name="com.android.cts.robot.ACTION_CANCEL" />
+ <action android:name="com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION" />
+ <action android:name="com.android.cts.robot.ACTION_INLINE_REPLY" />
</intent-filter>
</receiver>
-
-
</application>
</manifest>
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 2aa5f41..746b840 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -15,14 +15,23 @@
*/
package com.android.cts.robot;
-import android.app.Activity;
import android.app.Notification;
+import android.app.Notification.Action;
import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.os.SystemClock;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
public class NotificationBot extends BroadcastReceiver {
private static final String TAG = "NotificationBot";
@@ -30,6 +39,21 @@
private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+ private static final String ACTION_RESET_SETUP_NOTIFICATION =
+ "com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION";
+
+ private static final String ACTION_INLINE_REPLY =
+ "com.android.cts.robot.ACTION_INLINE_REPLY";
+
+ private static final String EXTRA_RESET_REPLY_PACKAGE = "EXTRA_RESET_REPLY_PACKAGE";
+ private static final String EXTRA_RESET_REPLY_ACTION = "EXTRA_RESET_REPLY_ACTION";
+ private static final String EXTRA_NOTIFICATION_TITLE = "EXTRA_NOTIFICATION_TITLE";
+
+ private static final String EXTRA_RESET_REPLY_ERROR = "EXTRA_RESET_REPLY_ERROR";
+
+ private static final String EXTRA_RESET_REQUEST_INTENT = "EXTRA_RESET_REQUEST_INTENT";
+
+ private static final String SUCCESS = "**SUCCESS**";
@Override
public void onReceive(Context context, Intent intent) {
@@ -60,8 +84,112 @@
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
noMa.cancel(id);
+ } else if (ACTION_RESET_SETUP_NOTIFICATION.equals(intent.getAction())) {
+ testShortcutResetSetupNotification(context, intent);
+
+ } else if (ACTION_INLINE_REPLY.equals(intent.getAction())) {
+ testShortcutResetInlineReplyReceived(context, intent);
+
} else {
Log.i(TAG, "received unexpected action: " + intent.getAction());
}
}
+
+ /**
+ * Test start request from CTS verifier. Show a notification with inline reply, which will
+ * trigger {@link #testShortcutResetInlineReplyReceived}.
+ */
+ private static void testShortcutResetSetupNotification(Context context, Intent intent) {
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.cancelAll();
+
+ final ShortcutManager sm = context.getSystemService(ShortcutManager.class);
+
+ final List<ShortcutInfo> EMPTY_LIST = new ArrayList<>();
+
+ long timeout = SystemClock.elapsedRealtime() + 10 * 1000;
+
+ // First, make sure this package is throttled.
+ while (!sm.isRateLimitingActive()) {
+ sm.setDynamicShortcuts(EMPTY_LIST);
+ try {
+ Thread.sleep(0);
+ } catch (InterruptedException e) {
+ }
+ if (SystemClock.elapsedRealtime() >= timeout) {
+ sendShortcutResetReply(context, intent,
+ "ShortcutMager rate-limiting not activated.");
+ return;
+ }
+ }
+
+ // Show a notification with inline reply.
+ final PendingIntent receiverIntent =
+ PendingIntent.getBroadcast(context, 0,
+ new Intent(ACTION_INLINE_REPLY)
+ .setComponent(new ComponentName(context, NotificationBot.class))
+ .putExtra(EXTRA_RESET_REQUEST_INTENT, intent),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ final RemoteInput ri = new RemoteInput.Builder("result")
+ .setLabel("Type something here and press send button").build();
+
+ final Notification.Builder nb = new Notification.Builder(context)
+ .setContentTitle(intent.getStringExtra(EXTRA_NOTIFICATION_TITLE))
+ .setSmallIcon(android.R.drawable.ic_popup_sync)
+ .addAction(new Action.Builder(0,
+ "Type something here and press send button", receiverIntent)
+ .addRemoteInput(ri)
+ .build());
+ context.getSystemService(NotificationManager.class).notify(0, nb.build());
+ }
+
+ /**
+ * Invoked when the inline reply from {@link #testShortcutResetSetupNotification} is performed.
+ *
+ * Check the shortcut manager rate-limiting state, and post the reply to CTS verifier.
+ */
+ private static void testShortcutResetInlineReplyReceived(Context context, Intent intent) {
+ Log.i(TAG, "Inline reply received");
+
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+ nm.cancelAll();
+
+ // Close notification shade.
+ context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+ // Check if rate-limiting has been reset.
+ final ShortcutManager sm = context.getSystemService(ShortcutManager.class);
+
+ String error;
+ final boolean success = !sm.isRateLimitingActive();
+ if (success) {
+ error = SUCCESS;
+ } else {
+ error = "Inline reply received, but ShortcutManager rate-limiting is still active.";
+ }
+
+ // Send back the result.
+ sendShortcutResetReply(context,
+ intent.getParcelableExtra(EXTRA_RESET_REQUEST_INTENT), error);
+ }
+
+ /**
+ * Reply an error message, or {@link #SUCCESS} for success, to CTS verifier for shortcut manager
+ * reset rate-limiting test.
+
+ * @param requestIntent original intent sent from the verifier to
+ * {@link #testShortcutResetSetupNotification}.
+ * @param error error message, or {@link #SUCCESS} if success.
+ */
+ private static void sendShortcutResetReply(Context context, Intent requestIntent, String error) {
+ final Intent replyIntent = new Intent();
+ replyIntent.setAction(requestIntent.getStringExtra(EXTRA_RESET_REPLY_ACTION));
+ replyIntent.putExtra(EXTRA_RESET_REPLY_ERROR, error);
+
+ if (error != null) {
+ Log.e(TAG, error);
+ }
+
+ context.sendBroadcast(replyIntent);
+ }
}