am 45037622: Merge "Add a test for package priority ranking." into lmp-sprout-dev

* commit '45037622b9b3d05f55499d3128fb301fdde056f0':
  Add a test for package priority ranking.
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index b066731..3ccbd07 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1080,6 +1080,15 @@
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
         </activity>
 
+        <activity android:name=".notifications.PackagePriorityVerifierActivity"
+                android:label="@string/package_priority_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+        </activity>
+
         <service android:name=".notifications.MockListener"
                  android:exported="true"
                  android:label="@string/nls_service_name"
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index e687087..429d379 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -968,6 +968,14 @@
         itself according to the current rotation of the device.</string>
 
     <string name="test_category_notifications">Notifications</string>
+    <string name="package_priority_test">Notification Package Priority Test</string>
+    <string name="package_priority_info">This test checks that the NotificationManagerService respects
+        user preferences about relative package priorities.
+    </string>
+    <string name="package_priority_high">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and mark it as having notification priority.</string>
+    <string name="package_priority_default">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and make sure it has default priority.</string>
+    <string name="package_priority_user_order">Check that ranker respects user priorities.</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/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index d65af80..b658e4f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -16,9 +16,7 @@
 
 package com.android.cts.verifier.notifications;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -38,20 +36,12 @@
 import android.widget.TextView;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.LinkedBlockingQueue;
 
-import static com.android.cts.verifier.notifications.MockListener.*;
-
 public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
         implements Runnable {
     private static final String TAG = "InteractiveVerifier";
@@ -223,17 +213,18 @@
     }
 
     protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
-        return createUserItem(parent, messageId, R.string.nls_start_settings);
+        return createUserItem(parent, R.string.nls_start_settings, messageId);
     }
 
-    protected View createRetryItem(ViewGroup parent, int messageId) {
-        return createUserItem(parent, messageId, R.string.attention_ready);
+    protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) {
+        return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs);
     }
 
-    protected View createUserItem(ViewGroup parent, int messageId, int actionId) {
+    protected View createUserItem(ViewGroup parent, int actionId, int messageId,
+            Object... messageFormatArgs) {
         View item = mInflater.inflate(R.layout.nls_item, parent, false);
         TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
-        instructions.setText(messageId);
+        instructions.setText(getString(messageId, messageFormatArgs));
         Button button = (Button) item.findViewById(R.id.nls_action_button);
         button.setText(actionId);
         button.setTag(actionId);
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 75eaebd..7207083 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -82,6 +82,7 @@
         Log.d(TAG, "created");
 
         mTestPackages.add("com.android.cts.verifier");
+        mTestPackages.add("com.android.cts.robot");
 
         mPosted = new ArrayList<String>();
         mRemoved = new ArrayList<String>();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
new file mode 100644
index 0000000..a6affb3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2014 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.app.Notification;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that the notification ranker honors user preferences about package priority.
+ * Users can, in Settings, specify a package as being high priority. This should
+ * result in the notificaitons from that package being ranked higher than those from
+ * other packages.
+ */
+public class PackagePriorityVerifierActivity
+        extends InteractiveVerifierActivity {
+    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 EXTRA_ID = "ID";
+    private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
+    private CharSequence mAppLabel;
+
+    @Override
+    int getTitleResource() {
+        return R.string.package_priority_test;
+    }
+
+    @Override
+    int getInstructionsResource() {
+        return R.string.package_priority_info;
+    }
+
+    // Test Setup
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        mAppLabel = getString(R.string.app_name);
+        List<InteractiveTestCase> tests = new ArrayList<>(17);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new WaitForSetPriorityDefault());
+        tests.add(new DefaultOrderTest());
+        tests.add(new WaitForSetPriorityHigh());
+        tests.add(new PackagePriorityOrderTest());
+        return tests;
+    }
+
+    // Tests
+
+    /** Wait for the user to set the target package priority to default. */
+    protected class WaitForSetPriorityDefault extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_default, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityDefault", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /** Wait for the user to set the target package priority to high. */
+    protected class WaitForSetPriorityHigh extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_high, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityHigh", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With default priority, the notifcations should be reverse-ordered by time.
+     * A is before B, and therefor should B should rank before A.
+     */
+    protected class DefaultOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_default_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankB != -1 && rankB < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") > rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With higher package priority, A should rank above B.
+     */
+    protected class PackagePriorityOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.package_priority_user_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankA != -1 && rankA < rankB) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") < rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+    // Utilities
+
+    private void sendNotifications() {
+        // post ours first, with an explicit time in the past to avoid any races.
+        Notification.Builder alice = new Notification.Builder(mContext)
+                .setContentTitle("alice title")
+                .setContentText("alice content")
+                .setSmallIcon(R.drawable.ic_stat_alice)
+                .setWhen(System.currentTimeMillis() - 10000L)
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        mNm.notify(0, alice.build());
+
+        // then post theirs, so it should be higher by default due to recency
+        Notification.Builder bob = new Notification.Builder(mContext)
+                .setContentTitle("bob title")
+                .setContentText("bob content")
+                .setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
+                .setWhen(System.currentTimeMillis())
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        Intent postIntent = new Intent(ACTION_POST);
+        postIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        postIntent.putExtra(EXTRA_ID, 0);
+        postIntent.putExtra(EXTRA_NOTIFICATION, bob.build());
+        sendBroadcast(postIntent);
+    }
+
+    private void cancelNotifications() {
+        //cancel ours
+        mNm.cancelAll();
+        //cancel theirs
+        Intent cancelIntent = new Intent(ACTION_CANCEL);
+        cancelIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        cancelIntent.putExtra(EXTRA_ID, 0);
+        sendBroadcast(cancelIntent);
+    }
+
+    /** Search a list of notification keys for a given packageName. */
+    private int indexOfPackageInKeys(List<String> orderedKeys, String packageName) {
+        for (int i = 0; i < orderedKeys.size(); i++) {
+            if (orderedKeys.get(i).contains(packageName)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}