OSDN Git Service

Adding some tests for request pin shortcut/widget flow
authorSunny Goyal <sunnygoyal@google.com>
Wed, 25 Jan 2017 19:19:59 +0000 (11:19 -0800)
committerSunny Goyal <sunnygoyal@google.com>
Wed, 22 Feb 2017 23:46:40 +0000 (15:46 -0800)
Bug: 33584624
Change-Id: I49df36f60d2ae071b9d2c77c9c3300e010cd3bb9

12 files changed:
tests/AndroidManifest-common.xml
tests/res/drawable/test_drawable_pin_item.xml [new file with mode: 0644]
tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java [new file with mode: 0644]
tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java [new file with mode: 0644]
tests/src/com/android/launcher3/testcomponent/WidgetConfigActivity.java
tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java
tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java [new file with mode: 0644]

index 24882aa..0a29147 100644 (file)
@@ -23,7 +23,9 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
 
-        <receiver android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig">
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+            android:label="No Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
             </intent-filter>
@@ -31,7 +33,9 @@
                        android:resource="@xml/appwidget_no_config" />
         </receiver>
 
-        <receiver android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig">
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+            android:label="With Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
             </intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
             </intent-filter>
         </activity>
+        <activity
+            android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
+            android:label="Test Pin Item"
+            android:icon="@drawable/test_drawable_pin_item">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/res/drawable/test_drawable_pin_item.xml b/tests/res/drawable/test_drawable_pin_item.xml
new file mode 100644 (file)
index 0000000..1d07256
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48.0"
+    android:viewportHeight="48.0">
+
+    <path
+        android:fillColor="#66000000"
+        android:pathData="M0,24a24,24 0 1,0 48,0a24,24 0 1,0 -48,0"/>
+    <path
+        android:fillColor="#FF1B5E20"
+        android:pathData="M2,24a22,22 0 1,0 44,0a22,22 0 1,0 -44,0"/>
+
+    <group
+        android:translateX="12"
+        android:translateY="12">
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
new file mode 100644 (file)
index 0000000..904590c
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Base activity with utility methods to help automate testing.
+ */
+public class BaseTestingActivity extends Activity implements View.OnClickListener {
+
+    public static final String SUFFIX_COMMAND = "-command";
+    public static final String EXTRA_METHOD = "method";
+    public static final String EXTRA_PARAM = "param_";
+
+    private static final int MARGIN_DP = 20;
+
+    private final String mAction = this.getClass().getName();
+
+    private LinearLayout mView;
+    private int mMargin;
+
+    private final BroadcastReceiver mCommandReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            handleCommand(intent);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mMargin = Math.round(TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, MARGIN_DP, getResources().getDisplayMetrics()));
+        mView = new LinearLayout(this);
+        mView.setPadding(mMargin, mMargin, mMargin, mMargin);
+        mView.setOrientation(LinearLayout.VERTICAL);
+        setContentView(mView);
+
+        registerReceiver(mCommandReceiver, new IntentFilter(mAction + SUFFIX_COMMAND));
+    }
+
+    protected void addButton(String title, String method) {
+        Button button = new Button(this);
+        button.setText(title);
+        button.setTag(method);
+        button.setOnClickListener(this);
+
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        lp.bottomMargin = mMargin;
+        mView.addView(button, lp);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+    }
+
+    @Override
+    protected void onDestroy() {
+        unregisterReceiver(mCommandReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onClick(View view) {
+        handleCommand(new Intent().putExtra(EXTRA_METHOD, (String) view.getTag()));
+    }
+
+    private void handleCommand(Intent cmd) {
+        String methodName = cmd.getStringExtra(EXTRA_METHOD);
+        try {
+            Method method = null;
+            for (Method m : this.getClass().getDeclaredMethods()) {
+                if (methodName.equals(m.getName()) &&
+                        !Modifier.isStatic(m.getModifiers()) &&
+                        Modifier.isPublic(m.getModifiers())) {
+                    method = m;
+                    break;
+                }
+            }
+            Object[] args = new Object[method.getParameterTypes().length];
+            Bundle extras = cmd.getExtras();
+            for (int i = 0; i < args.length; i++) {
+                args[i] = extras.get(EXTRA_PARAM + i);
+            }
+            method.invoke(this, args);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Intent getCommandIntent(Class<?> clazz, String method) {
+        return new Intent(clazz.getName() + SUFFIX_COMMAND)
+                .putExtra(EXTRA_METHOD, method);
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java b/tests/src/com/android/launcher3/testcomponent/RequestPinItemActivity.java
new file mode 100644 (file)
index 0000000..c2dd225
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+/**
+ * Sample activity to request pinning an item.
+ */
+@TargetApi(26)
+public class RequestPinItemActivity extends BaseTestingActivity {
+
+    private PendingIntent mCallback = null;
+    private String mShortcutId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addButton("Pin Shortcut", "pinShortcut");
+        addButton("Pin Widget without config ", "pinWidgetNoConfig");
+        addButton("Pin Widget with config", "pinWidgetWithConfig");
+    }
+
+    public void setCallback(PendingIntent callback) {
+        mCallback = callback;
+    }
+
+    public void setShortcutId(String id) {
+        mShortcutId = id;
+    }
+
+    public void pinShortcut() {
+        ShortcutManager sm = getSystemService(ShortcutManager.class);
+
+        // Generate icon
+        int r = sm.getIconMaxWidth() / 2;
+        Bitmap icon = Bitmap.createBitmap(r * 2, r * 2, Bitmap.Config.ARGB_8888);
+        Paint p = new Paint();
+        p.setColor(Color.RED);
+        new Canvas(icon).drawCircle(r, r, r, p);
+
+        ShortcutInfo info = new ShortcutInfo.Builder(this, mShortcutId)
+                .setIntent(getPackageManager().getLaunchIntentForPackage(getPackageName()))
+                .setIcon(Icon.createWithBitmap(icon))
+                .setShortLabel("Test shortcut")
+                .build();
+
+        IntentSender callback = mCallback == null ? null : mCallback.getIntentSender();
+        sm.requestPinShortcut(info, callback);
+    }
+
+    public void pinWidgetNoConfig() {
+        requestWidget(new ComponentName(this, AppWidgetNoConfig.class));
+    }
+
+    public void pinWidgetWithConfig() {
+        requestWidget(new ComponentName(this, AppWidgetWithConfig.class));
+    }
+
+    private void requestWidget(ComponentName cn) {
+        AppWidgetManager.getInstance(this).requestPinAppWidget(cn, mCallback);
+    }
+}
index c0509bc..d76ad04 100644 (file)
  */
 package com.android.launcher3.testcomponent;
 
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Bundle;
 
 /**
  * Simple activity for widget configuration
  */
-public class WidgetConfigActivity extends Activity {
+public class WidgetConfigActivity extends BaseTestingActivity {
 
     public static final String SUFFIX_FINISH = "-finish";
     public static final String EXTRA_CODE = "code";
-    public static final String EXTRA_INTENT = "intent";
-
-    private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            WidgetConfigActivity.this.setResult(
-                    intent.getIntExtra(EXTRA_CODE, RESULT_CANCELED),
-                    (Intent) intent.getParcelableExtra(EXTRA_INTENT));
-            WidgetConfigActivity.this.finish();
-        }
-    };
-
-    private final String mAction = this.getClass().getName();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        registerReceiver(mFinishReceiver, new IntentFilter(mAction + SUFFIX_FINISH));
+        addButton("Cancel", "clickCancel");
+        addButton("OK", "clickOK");
     }
 
-    @Override
-    protected void onResume() {
-        super.onResume();
-        sendBroadcast(new Intent(mAction).putExtra(Intent.EXTRA_INTENT, getIntent()));
+    public void clickCancel() {
+        setResult(RESULT_CANCELED);
+        finish();
     }
 
-    @Override
-    protected void onDestroy() {
-        unregisterReceiver(mFinishReceiver);
-        super.onDestroy();
+    public void clickOK() {
+        setResult(RESULT_OK);
+        finish();
     }
 }
index 3f77bfd..9361750 100644 (file)
@@ -22,6 +22,7 @@ public class AllAppsIconToHomeTest extends LauncherInstrumentationTestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setDefaultLauncher();
 
         mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
                 .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
index 4bc40c6..1ed4a24 100644 (file)
  */
 package com.android.launcher3.ui;
 
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Point;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -46,16 +50,24 @@ import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 
+import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Locale;
 import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Base class for all instrumentation tests providing various utility methods.
  */
 public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
 
+    public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+    public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+
     public static final long DEFAULT_UI_TIMEOUT = 3000;
     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
@@ -89,11 +101,14 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
      * Starts the launcher activity in the target package and returns the Launcher instance.
      */
     protected Launcher startLauncher() {
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+        return (Launcher) getInstrumentation().startActivitySync(getHomeIntent());
+    }
+
+    protected Intent getHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
                 .setPackage(mTargetPackage)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return (Launcher) getInstrumentation().startActivitySync(homeIntent);
     }
 
     /**
@@ -104,16 +119,31 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
         if (mTargetContext.getPackageManager().checkPermission(
                 mTargetPackage, android.Manifest.permission.BIND_APPWIDGET)
                 != PackageManager.PERMISSION_GRANTED) {
-            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
-                    "appwidget grantbind --package " + mTargetPackage);
-            // Read the input stream fully.
-            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-            while (fis.read() != -1);
-            fis.close();
+            runShellCommand("appwidget grantbind --package " + mTargetPackage);
         }
     }
 
     /**
+     * Sets the target launcher as default launcher.
+     */
+    protected void setDefaultLauncher() throws IOException {
+        ActivityInfo launcher = mTargetContext.getPackageManager()
+                .queryIntentActivities(getHomeIntent(), 0).get(0).activityInfo;
+        runShellCommand("cmd package set-home-activity " +
+                new ComponentName(launcher.packageName, launcher.name).flattenToString());
+    }
+
+    protected void runShellCommand(String command) throws IOException {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+
+        // Read the input stream fully.
+        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        while (fis.read() != -1);
+        fis.close();
+    }
+
+    /**
      * Opens all apps and returns the recycler view
      */
     protected UiObject2 openAllApps() {
@@ -285,4 +315,35 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
         String name = mTargetContext.getResources().getResourceEntryName(id);
         return By.res(mTargetPackage, name);
     }
+
+
+    /**
+     * Broadcast receiver which blocks until the result is received.
+     */
+    public class BlockingBroadcastReceiver extends BroadcastReceiver {
+
+        private final CountDownLatch latch = new CountDownLatch(1);
+        private Intent mIntent;
+
+        public BlockingBroadcastReceiver(String action) {
+            mTargetContext.registerReceiver(this, new IntentFilter(action));
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntent = intent;
+            latch.countDown();
+        }
+
+        public Intent blockingGetIntent() throws InterruptedException {
+            latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+            mTargetContext.unregisterReceiver(this);
+            return mIntent;
+        }
+
+        public Intent blockingGetExtraIntent() throws InterruptedException {
+            Intent intent = blockingGetIntent();
+            return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        }
+    }
 }
index c6828f0..ee3a628 100644 (file)
@@ -25,6 +25,7 @@ public class ShortcutsLaunchTest extends LauncherInstrumentationTestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setDefaultLauncher();
 
         mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
                 .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
index d573eea..061e865 100644 (file)
@@ -25,6 +25,7 @@ public class ShortcutsToHomeTest extends LauncherInstrumentationTestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setDefaultLauncher();
 
         mSettingsApp = LauncherAppsCompat.getInstance(mTargetContext)
                 .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
index 7cbd292..0b4e34f 100644 (file)
@@ -18,10 +18,7 @@ package com.android.launcher3.ui.widget;
 import android.app.Activity;
 import android.app.Application;
 import android.appwidget.AppWidgetManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject2;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -41,8 +38,6 @@ import com.android.launcher3.util.Wait;
 import com.android.launcher3.widget.WidgetCell;
 
 import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Test to verify widget configuration is properly shown.
@@ -50,9 +45,6 @@ import java.util.concurrent.TimeUnit;
 @LargeTest
 public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
 
-    public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
-    public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
-
     private LauncherAppWidgetProviderInfo mWidgetInfo;
     private SimpleActivityMonitor mActivityMonitor;
     private MainThreadExecutor mMainThreadExecutor;
@@ -69,6 +61,8 @@ public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
                 .registerActivityLifecycleCallbacks(mActivityMonitor);
         mMainThreadExecutor = new MainThreadExecutor();
         mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
+
+        grantWidgetPermission();
     }
 
     @Override
@@ -126,12 +120,11 @@ public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
         // Verify that the widget id is valid and bound
         assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
 
+        setResult(acceptConfig);
         if (acceptConfig) {
-            setResult(Activity.RESULT_OK);
             assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT));
             assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
         } else {
-            setResult(Activity.RESULT_CANCELED);
             // Verify that the widget id is deleted.
             assertTrue(Wait.atMost(new Condition() {
                 @Override
@@ -142,10 +135,11 @@ public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
         }
     }
 
-    private void setResult(int resultCode) {
-        String action = WidgetConfigActivity.class.getName() + WidgetConfigActivity.SUFFIX_FINISH;
+    private void setResult(boolean success) {
+
         getInstrumentation().getTargetContext().sendBroadcast(
-                new Intent(action).putExtra(WidgetConfigActivity.EXTRA_CODE, resultCode));
+                WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
+                        success ? "clickOK" : "clickCancel"));
     }
 
     /**
@@ -185,28 +179,17 @@ public class AddConfigWidgetTest extends LauncherInstrumentationTestCase {
     /**
      * Broadcast receiver for receiving widget config activity status.
      */
-    private class WidgetConfigStartupMonitor extends BroadcastReceiver {
-
-        private final CountDownLatch latch = new CountDownLatch(1);
-        private Intent mIntent;
+    private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
 
-        WidgetConfigStartupMonitor() {
-            getInstrumentation().getTargetContext().registerReceiver(this,
-                    new IntentFilter(WidgetConfigActivity.class.getName()));
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-            latch.countDown();
+        public WidgetConfigStartupMonitor() {
+            super(WidgetConfigActivity.class.getName());
         }
 
         public int getWidgetId() throws InterruptedException {
-            latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
-            getInstrumentation().getTargetContext().unregisterReceiver(this);
-            assertNotNull(mIntent);
-            assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, mIntent.getAction());
-            int widgetId = mIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+            Intent intent = blockingGetExtraIntent();
+            assertNotNull(intent);
+            assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction());
+            int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                     LauncherAppWidgetInfo.NO_ID);
             assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID);
             return widgetId;
index b7e1ca9..3c92c57 100644 (file)
@@ -41,6 +41,7 @@ public class AddWidgetTest extends LauncherInstrumentationTestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        grantWidgetPermission();
 
         widgetInfo = findWidgetProvider(false /* hasConfigureScreen */);
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
new file mode 100644 (file)
index 0000000..5ef5ec1
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.ui.widget;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.testcomponent.AppWidgetNoConfig;
+import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+import com.android.launcher3.testcomponent.RequestPinItemActivity;
+import com.android.launcher3.ui.LauncherInstrumentationTestCase;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.SimpleActivityMonitor;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.widget.WidgetCell;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+/**
+ * Test to verify pin item request flow.
+ */
+@LargeTest
+public class RequestPinItemTest  extends LauncherInstrumentationTestCase {
+
+    private SimpleActivityMonitor mActivityMonitor;
+    private MainThreadExecutor mMainThreadExecutor;
+
+    private String mCallbackAction;
+    private String mShortcutId;
+    private int mAppWidgetId;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        grantWidgetPermission();
+        setDefaultLauncher();
+
+        mActivityMonitor = new SimpleActivityMonitor();
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .registerActivityLifecycleCallbacks(mActivityMonitor);
+        mMainThreadExecutor = new MainThreadExecutor();
+
+        mCallbackAction = UUID.randomUUID().toString();
+        mShortcutId = UUID.randomUUID().toString();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ((Application) getInstrumentation().getTargetContext().getApplicationContext())
+                .unregisterActivityLifecycleCallbacks(mActivityMonitor);
+        super.tearDown();
+    }
+
+    public void testPinWidgetNoConfig() throws Throwable {
+        runTest("pinWidgetNoConfig", true, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                                .equals(AppWidgetNoConfig.class.getName());
+            }
+        });
+    }
+
+    public void testPinWidgetWithConfig() throws Throwable {
+        runTest("pinWidgetWithConfig", true, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof LauncherAppWidgetInfo &&
+                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                                .equals(AppWidgetWithConfig.class.getName());
+            }
+        });
+    }
+
+
+    public void testPinWidgetShortcut() throws Throwable {
+        runTest("pinShortcut", false, new ItemOperator() {
+            @Override
+            public boolean evaluate(ItemInfo info, View view) {
+                return info instanceof ShortcutInfo &&
+                        info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+                        ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
+            }
+        });
+    }
+
+    private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher)
+            throws Throwable {
+        if (!Utilities.isAtLeastO()) {
+            return;
+        }
+        lockRotation(true);
+
+        clearHomescreen();
+        startLauncher();
+
+        // Open all apps and wait for load complete
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        // Open Pin item activity
+        BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
+                RequestPinItemActivity.class.getName());
+        scrollAndFind(appsContainer, By.text("Test Pin Item")).click();
+        assertNotNull(openMonitor.blockingGetExtraIntent());
+
+        // Set callback
+        PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
+                new Intent(mCallbackAction), PendingIntent.FLAG_ONE_SHOT);
+        mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, "setCallback").putExtra(
+                RequestPinItemActivity.EXTRA_PARAM + "0", callback));
+
+        if (!isWidget) {
+            // Set shortcut id
+            mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+                    RequestPinItemActivity.class, "setShortcutId").putExtra(
+                    RequestPinItemActivity.EXTRA_PARAM + "0", mShortcutId));
+        }
+
+        // call the requested method to start the flow
+        mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
+                RequestPinItemActivity.class, activityMethod));
+        UiObject2 widgetCell = mDevice.wait(
+                Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
+        assertNotNull(widgetCell);
+
+        // Accept confirmation:
+        BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
+        mDevice.wait(Until.findObject(By.text(mTargetContext.getString(
+                R.string.place_automatically).toUpperCase())), DEFAULT_UI_TIMEOUT).click();
+        Intent result = resultReceiver.blockingGetIntent();
+        assertNotNull(result);
+        mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+        if (isWidget) {
+            assertNotSame(-1, mAppWidgetId);
+        }
+
+        // Go back to home
+        mTargetContext.startActivity(getHomeIntent());
+        assertTrue(Wait.atMost(new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT));
+    }
+
+    /**
+     * Condition for for an item
+     */
+    private class ItemSearchCondition extends Condition implements Callable<Boolean> {
+
+        private final ItemOperator mOp;
+
+        ItemSearchCondition(ItemOperator op) {
+            mOp = op;
+        }
+
+        @Override
+        public boolean isTrue() throws Throwable {
+            return mMainThreadExecutor.submit(this).get();
+        }
+
+        @Override
+        public Boolean call() throws Exception {
+            // Find the resumed launcher
+            Launcher launcher = null;
+            for (Activity a : mActivityMonitor.resumed) {
+                if (a instanceof Launcher) {
+                    launcher = (Launcher) a;
+                }
+            }
+            if (launcher == null) {
+                return false;
+            }
+            return launcher.getWorkspace().getFirstMatch(mOp) != null;
+        }
+    }
+}