Improve shortcut demo

- Add launcher demo
- Now we build two of launchers and publishers, for better testing

Bug 27548047

Change-Id: Iede6abcf611261c83b7486f07709b56c6f5c53e0
diff --git a/samples/ShortcutDemo/Android.mk b/samples/ShortcutDemo/Android.mk
index 811649c..2ef28ee 100644
--- a/samples/ShortcutDemo/Android.mk
+++ b/samples/ShortcutDemo/Android.mk
@@ -1,16 +1,17 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+# 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.
 
-LOCAL_MODULE_TAGS := samples tests
+LOCAL_PATH := $(call my-dir)
 
-# Only compile source java files in this apk.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := ShortcutDemo
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/samples/ShortcutDemo/common/res/layout/list_item.xml b/samples/ShortcutDemo/common/res/layout/list_item.xml
new file mode 100644
index 0000000..c99cc66
--- /dev/null
+++ b/samples/ShortcutDemo/common/res/layout/list_item.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+>
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:layout_marginBottom="8dip"
+        />
+    <LinearLayout
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical"
+        android:paddingLeft="8dip"
+        >
+        <TextView
+            android:id="@+id/line1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="#000000"
+            android:textSize="16sp"
+            />
+        <TextView
+            android:id="@+id/line2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="#444444"
+        />
+    </LinearLayout>
+    <Button
+        android:id="@+id/launch"
+        android:text="@string/launch"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:gravity="center"
+        style="@android:style/Widget.Material.Button.Borderless"/>
+    <Button
+        android:id="@+id/toggle"
+        android:text="@string/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:gravity="center"
+        style="@android:style/Widget.Material.Button.Borderless"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/common/res/values/strings.xml
similarity index 80%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/common/res/values/strings.xml
index c466993..9ec96f1 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/common/res/values/strings.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
+    <add-resource type="string" name="launch"/>
+    <string name="launch">Launch</string>
+
+    <add-resource type="string" name="toggle"/>
+    <string name="toggle">Toggle</string>
 </resources>
diff --git a/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/AppLabelCache.java b/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/AppLabelCache.java
new file mode 100644
index 0000000..ef05df5
--- /dev/null
+++ b/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/AppLabelCache.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.pm.shortcutdemo;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.ArrayMap;
+
+public class AppLabelCache {
+    private final Context mContext;
+    private ArrayMap<String, String> mAppNames = new ArrayMap<>();
+
+    public AppLabelCache(Context context) {
+        mContext = context;
+    }
+
+    public String getAppLabel(String packageName) {
+        String name = mAppNames.get(packageName);
+        if (name != null) {
+            return name;
+        }
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            final ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+            name = pm.getApplicationLabel(ai).toString();
+        } catch (NameNotFoundException e) {
+            return packageName;
+        }
+        mAppNames.put(packageName, name);
+        return name;
+    }
+}
diff --git a/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/ShortcutAdapter.java b/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/ShortcutAdapter.java
new file mode 100644
index 0000000..4c28b1c
--- /dev/null
+++ b/samples/ShortcutDemo/common/src/com/example/android/pm/shortcutdemo/ShortcutAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * 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.example.android.pm.shortcutdemo;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.List;
+
+public abstract class ShortcutAdapter extends BaseAdapter implements OnClickListener {
+    public static final String TAG = "ShortcutDemo";
+
+    private final Context mContext;
+    private final LayoutInflater mInflater;
+    private LauncherApps mLauncherApps;
+    private final AppLabelCache mAppLabelCache;
+    private List<ShortcutInfo> mShortcuts;
+
+    public ShortcutAdapter(Context context) {
+        mContext = context;
+        mAppLabelCache = new AppLabelCache(mContext);
+        mInflater = mContext.getSystemService(LayoutInflater.class);
+        mLauncherApps = mContext.getSystemService(LauncherApps.class);
+    }
+
+    protected abstract int getLayoutId();
+    protected abstract int getText1Id();
+    protected abstract int getText2Id();
+    protected abstract int getImageId();
+    protected abstract int getLaunchId();
+    protected abstract int getToggleId();
+
+    protected boolean showLaunch() {
+        return false;
+    }
+
+    protected boolean showToggle() {
+        return false;
+    }
+
+    protected String getToggleText(ShortcutInfo si) {
+        return "Toggle";
+    }
+
+    protected void onLaunchClicked(ShortcutInfo si) {
+    }
+
+    protected void onToggleClicked(ShortcutInfo si) {
+    }
+
+    public void setShortcuts(List<ShortcutInfo> shortcuts) {
+        mShortcuts = shortcuts;
+        notifyDataSetChanged();
+    }
+
+    public List<ShortcutInfo> getShortcuts() {
+        return mShortcuts;
+    }
+
+    @Override
+    public int getCount() {
+        return mShortcuts == null ? 0 : mShortcuts.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mShortcuts.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final View view;
+        if (convertView != null) {
+            view = convertView;
+        } else {
+            view = mInflater.inflate(getLayoutId(), null);
+        }
+
+        bindView(view, position, mShortcuts.get(position));
+
+        return view;
+    }
+
+    public void bindView(View view, int position, ShortcutInfo si) {
+        if (showLaunch()) {
+            final View v = view.findViewById(getLaunchId());
+            v.setOnClickListener(this);
+        }
+        if (showToggle()) {
+            final Button v = (Button) view.findViewById(getToggleId());
+            v.setOnClickListener(this);
+            v.setText(getToggleText(si));
+        }
+
+        final TextView line1 = (TextView) view.findViewById(getText1Id());
+        final TextView line2 = (TextView) view.findViewById(getText2Id());
+
+        view.setTag(si);
+
+        line1.setText(si.getTitle());
+        line2.setText(
+                si.getId() + (si.isDynamic() ? " [dynamic]" : "")
+                        + (si.isPinned() ? " [pinned]" : "") + "\n"
+                + mAppLabelCache.getAppLabel(si.getPackageName()));
+
+        // view.setBackgroundColor(si.isPinned() ? Color.rgb(255, 255, 192) : Color.WHITE);
+
+        // TODO Do it on worker thread
+        final ImageView image = (ImageView) view.findViewById(getImageId());
+        Bitmap icon = null;
+        if (si.hasIconResource()) {
+            try {
+                final Resources res = mContext.getPackageManager().getResourcesForApplication(
+                        si.getPackageName());
+                icon = BitmapFactory.decodeResource(res,
+                        mLauncherApps.getShortcutIconResId(si, Process.myUserHandle()));
+
+            } catch (NameNotFoundException e) {
+                Log.w(TAG, "Unable to load icon from " + si.getPackageName(), e);
+            }
+        } else if (si.hasIconFile()) {
+            icon = pfdToBitmap(mLauncherApps.getShortcutIconFd(si, Process.myUserHandle()));
+        }
+        image.setImageBitmap(icon);
+    }
+
+    private Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
+        if (pfd == null) {
+            return null;
+        }
+        try {
+            final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+            if (bmp == null) {
+                Log.w(TAG, "Failed to decode icon");
+            }
+            return bmp;
+        } finally {
+            try {
+                pfd.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        final ShortcutInfo si = (ShortcutInfo)(((View) v.getParent()).getTag());
+        if (v.getId() == getLaunchId()) {
+            onLaunchClicked(si);
+        } else if (v.getId() == getToggleId()) {
+            onToggleClicked(si);
+        }
+    }
+}
diff --git a/samples/ShortcutDemo/launcher/Android.mk b/samples/ShortcutDemo/launcher/Android.mk
new file mode 100644
index 0000000..2d69320
--- /dev/null
+++ b/samples/ShortcutDemo/launcher/Android.mk
@@ -0,0 +1,59 @@
+#
+# 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.
+#
+
+# We build two apps from the same source
+
+LOCAL_PATH:= $(call my-dir)
+
+# === App 1 ===
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := ShortcutLauncherDemo
+
+LOCAL_MODULE_TAGS := samples tests
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.example.android.pm.shortcutlauncherdemo
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../common/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res1
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# === App 2 ===
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := ShortcutLauncherDemo2
+
+LOCAL_MODULE_TAGS := samples tests
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.example.android.pm.shortcutlauncherdemo2
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../common/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/ShortcutDemo/AndroidManifest.xml b/samples/ShortcutDemo/launcher/AndroidManifest.xml
similarity index 72%
copy from samples/ShortcutDemo/AndroidManifest.xml
copy to samples/ShortcutDemo/launcher/AndroidManifest.xml
index bce132f..c3257bb 100644
--- a/samples/ShortcutDemo/AndroidManifest.xml
+++ b/samples/ShortcutDemo/launcher/AndroidManifest.xml
@@ -15,22 +15,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.pm.shortcuts">
+    package="com.example.android.pm.shortcutlauncherdemo">
 
     <uses-sdk android:minSdkVersion="24" />
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <application android:label="@string/app_title">
-        <activity android:name="Main">
+    <application android:label="@string/app_title"
+        android:resizeableActivity="true">
+        <activity android:name="com.example.android.pm.shortcutlauncherdemo.Main">
             <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:authorities="com.example.android.pm.shortcuts"
-            android:name="IconProvider"/>
     </application>
 </manifest>
diff --git a/samples/ShortcutDemo/res/layout/main.xml b/samples/ShortcutDemo/launcher/res/layout/main.xml
similarity index 77%
copy from samples/ShortcutDemo/res/layout/main.xml
copy to samples/ShortcutDemo/launcher/res/layout/main.xml
index c59aedc..bb24877 100644
--- a/samples/ShortcutDemo/res/layout/main.xml
+++ b/samples/ShortcutDemo/launcher/res/layout/main.xml
@@ -17,12 +17,14 @@
         android:orientation="vertical"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
-    <Button
-            android:id="@+id/button1"
-            android:text="@string/publish"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:onClick="onPublishPressed"/>
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:choiceMode="multipleChoice"
+        android:enabled="true"
+        />
 </LinearLayout>
 
 
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/launcher/res/values/strings.xml
similarity index 87%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/launcher/res/values/strings.xml
index c466993..66c78aa 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/launcher/res/values/strings.xml
@@ -15,6 +15,4 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
 </resources>
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/launcher/res1/values/strings.xml
similarity index 87%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/launcher/res1/values/strings.xml
index c466993..ba09d82 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/launcher/res1/values/strings.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
+    <string name="app_title">[L1] Shortcuts Launcher Demo</string>
 </resources>
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/launcher/res2/values/strings.xml
similarity index 87%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/launcher/res2/values/strings.xml
index c466993..f27cccc 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/launcher/res2/values/strings.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
+    <string name="app_title">[L2] Shortcuts Launcher Demo</string>
 </resources>
diff --git a/samples/ShortcutDemo/launcher/src/com/example/android/pm/shortcutlauncherdemo/Main.java b/samples/ShortcutDemo/launcher/src/com/example/android/pm/shortcutlauncherdemo/Main.java
new file mode 100644
index 0000000..20cd4ec
--- /dev/null
+++ b/samples/ShortcutDemo/launcher/src/com/example/android/pm/shortcutlauncherdemo/Main.java
@@ -0,0 +1,233 @@
+/*
+ * 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.example.android.pm.shortcutlauncherdemo;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListView;
+
+import com.example.android.pm.shortcutdemo.ShortcutAdapter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class Main extends ListActivity {
+    public static final String TAG = "ShortcutLauncherDemo";
+
+    private LauncherApps mLauncherApps;
+
+    private MyAdapter mAdapter;
+
+    private ArrayMap<String, String> mAppNames = new ArrayMap<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.main);
+
+        mLauncherApps = getSystemService(LauncherApps.class);
+        mLauncherApps.registerCallback(mLauncherCallback);
+
+        mAdapter = new MyAdapter(this);
+
+        setListAdapter(mAdapter);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        refreshList();
+    }
+
+    @Override
+    protected void onDestroy() {
+        mLauncherApps.unregisterCallback(mLauncherCallback);
+
+        super.onDestroy();
+    }
+
+    private void togglePin(ShortcutInfo selected) {
+        final String packageName = selected.getPackageName();
+
+        final List<String> pinned = new ArrayList<>();
+        for (ShortcutInfo si : mAdapter.getShortcuts()) {
+            if (si.isPinned() && si.getPackageName().equals(packageName)) {
+                pinned.add(si.getId());
+            }
+        }
+        if (selected.isPinned()) {
+            pinned.remove(selected.getId());
+        } else {
+            pinned.add(selected.getId());
+        }
+        mLauncherApps.pinShortcuts(packageName, pinned, Process.myUserHandle());
+    }
+
+    private void launch(ShortcutInfo si) {
+        mLauncherApps.startShortcut(si.getPackageName(), si.getId(), null, null,
+                Process.myUserHandle());
+    }
+
+    private String getAppLabel(String packageName) {
+        String name = mAppNames.get(packageName);
+        if (name != null) {
+            return name;
+        }
+        PackageManager pm = getPackageManager();
+        try {
+            final ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+            return pm.getApplicationLabel(ai).toString();
+        } catch (NameNotFoundException e) {
+            return packageName;
+        }
+    }
+
+    private void refreshList() {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+
+        final List<ShortcutInfo> list = mLauncherApps.getShortcuts(q, Process.myUserHandle());
+        Collections.sort(list, mShortcutComparator);
+        Log.i(TAG, "All shortcuts:");
+        for (ShortcutInfo si : list) {
+            Log.i(TAG, si.toString());
+        }
+
+        mAdapter.setShortcuts(list);
+    }
+
+    private final Comparator<ShortcutInfo> mShortcutComparator =
+            (ShortcutInfo s1, ShortcutInfo s2) -> {
+                int ret = 0;
+                ret = getAppLabel(s1.getPackageName()).compareTo(getAppLabel(s2.getPackageName()));
+                if (ret != 0) return ret;
+
+                ret = s1.getTitle().compareTo(s2.getTitle());
+                if (ret != 0) return ret;
+
+                ret = s1.getId().compareTo(s2.getId());
+                if (ret != 0) return ret;
+
+                return 0;
+            };
+
+    private final LauncherApps.Callback mLauncherCallback = new LauncherApps.Callback() {
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onShortcutsChanged(String packageName,
+                List<ShortcutInfo> shortcuts, UserHandle user) {
+            Log.w(TAG, "onShortcutsChanged: user=" + user + " package=" + packageName);
+            refreshList();
+        }
+    };
+
+    class MyAdapter extends ShortcutAdapter {
+        public MyAdapter(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected int getLayoutId() {
+            return R.layout.list_item;
+        }
+
+        @Override
+        protected int getText1Id() {
+            return R.id.line1;
+        }
+
+        @Override
+        protected int getText2Id() {
+            return R.id.line2;
+        }
+
+        @Override
+        protected int getImageId() {
+            return R.id.image;
+        }
+
+        @Override
+        protected int getLaunchId() {
+            return R.id.launch;
+        }
+
+        @Override
+        protected int getToggleId() {
+            return R.id.toggle;
+        }
+
+        @Override
+        protected boolean showLaunch() {
+            return true;
+        }
+
+        @Override
+        protected boolean showToggle() {
+            return true;
+        }
+
+        @Override
+        protected String getToggleText(ShortcutInfo si) {
+            return si.isPinned() ? "Unpin" : "Pin";
+        }
+
+        @Override
+        protected void onLaunchClicked(ShortcutInfo si) {
+            launch(si);
+        }
+
+        @Override
+        protected void onToggleClicked(ShortcutInfo si) {
+            togglePin(si);
+        }
+    }
+}
diff --git a/samples/ShortcutDemo/publisher/Android.mk b/samples/ShortcutDemo/publisher/Android.mk
new file mode 100644
index 0000000..bf0ac47
--- /dev/null
+++ b/samples/ShortcutDemo/publisher/Android.mk
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+# We build two apps from the same source
+
+LOCAL_PATH:= $(call my-dir)
+
+# === App 1 ===
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := ShortcutDemo
+
+LOCAL_MODULE_TAGS := samples tests
+
+LOCAL_MANIFEST_FILE := manifest1/AndroidManifest.xml
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.example.android.pm.shortcutdemo
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+#LOCAL_SRC_FILES += $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../common/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res1
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# === App 2 ===
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := ShortcutDemo2
+
+LOCAL_MODULE_TAGS := samples tests
+
+LOCAL_MANIFEST_FILE := manifest2/AndroidManifest.xml
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.example.android.pm.shortcutdemo2
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+#LOCAL_SRC_FILES += $(call all-java-files-under, ../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/../common/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/ShortcutDemo/AndroidManifest.xml b/samples/ShortcutDemo/publisher/manifest1/AndroidManifest.xml
similarity index 89%
rename from samples/ShortcutDemo/AndroidManifest.xml
rename to samples/ShortcutDemo/publisher/manifest1/AndroidManifest.xml
index bce132f..1a3ed36 100644
--- a/samples/ShortcutDemo/AndroidManifest.xml
+++ b/samples/ShortcutDemo/publisher/manifest1/AndroidManifest.xml
@@ -15,14 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.pm.shortcuts">
+    package="com.example.android.pm.shortcutdemo">
 
     <uses-sdk android:minSdkVersion="24" />
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <application android:label="@string/app_title">
+    <application android:label="@string/app_title"
+        android:resizeableActivity="true">
         <activity android:name="Main">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -30,7 +31,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <provider android:authorities="com.example.android.pm.shortcuts"
+        <provider android:authorities="com.example.android.pm.shortcutdemo"
             android:name="IconProvider"/>
     </application>
 </manifest>
diff --git a/samples/ShortcutDemo/AndroidManifest.xml b/samples/ShortcutDemo/publisher/manifest2/AndroidManifest.xml
similarity index 89%
copy from samples/ShortcutDemo/AndroidManifest.xml
copy to samples/ShortcutDemo/publisher/manifest2/AndroidManifest.xml
index bce132f..887f379 100644
--- a/samples/ShortcutDemo/AndroidManifest.xml
+++ b/samples/ShortcutDemo/publisher/manifest2/AndroidManifest.xml
@@ -15,14 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.pm.shortcuts">
+    package="com.example.android.pm.shortcutdemo">
 
     <uses-sdk android:minSdkVersion="24" />
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <application android:label="@string/app_title">
+    <application android:label="@string/app_title"
+        android:resizeableActivity="true">
         <activity android:name="Main">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -30,7 +31,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <provider android:authorities="com.example.android.pm.shortcuts"
+        <provider android:authorities="com.example.android.pm.shortcutdemo2"
             android:name="IconProvider"/>
     </application>
 </manifest>
diff --git a/samples/ShortcutDemo/res/drawable-nodpi/icon1.png b/samples/ShortcutDemo/publisher/res/drawable-nodpi/icon1.png
similarity index 100%
rename from samples/ShortcutDemo/res/drawable-nodpi/icon1.png
rename to samples/ShortcutDemo/publisher/res/drawable-nodpi/icon1.png
Binary files differ
diff --git a/samples/ShortcutDemo/res/drawable-nodpi/icon2.png b/samples/ShortcutDemo/publisher/res/drawable-nodpi/icon2.png
similarity index 100%
rename from samples/ShortcutDemo/res/drawable-nodpi/icon2.png
rename to samples/ShortcutDemo/publisher/res/drawable-nodpi/icon2.png
Binary files differ
diff --git a/samples/ShortcutDemo/res/drawable-nodpi/icon_large_1.png b/samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_1.png
similarity index 100%
rename from samples/ShortcutDemo/res/drawable-nodpi/icon_large_1.png
rename to samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_1.png
Binary files differ
diff --git a/samples/ShortcutDemo/res/drawable-nodpi/icon_large_2.png b/samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_2.png
similarity index 100%
rename from samples/ShortcutDemo/res/drawable-nodpi/icon_large_2.png
rename to samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_2.png
Binary files differ
diff --git a/samples/ShortcutDemo/res/drawable-nodpi/icon_large_3.png b/samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_3.png
similarity index 100%
rename from samples/ShortcutDemo/res/drawable-nodpi/icon_large_3.png
rename to samples/ShortcutDemo/publisher/res/drawable-nodpi/icon_large_3.png
Binary files differ
diff --git a/samples/ShortcutDemo/res/layout/main.xml b/samples/ShortcutDemo/publisher/res/layout/main.xml
similarity index 80%
rename from samples/ShortcutDemo/res/layout/main.xml
rename to samples/ShortcutDemo/publisher/res/layout/main.xml
index c59aedc..f2dc492 100644
--- a/samples/ShortcutDemo/res/layout/main.xml
+++ b/samples/ShortcutDemo/publisher/res/layout/main.xml
@@ -18,11 +18,17 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
     <Button
-            android:id="@+id/button1"
+            android:id="@+id/publish"
             android:text="@string/publish"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:onClick="onPublishPressed"/>
+    <Button
+        android:id="@+id/delete_all"
+        android:text="@string/delete_all"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onDeleteAllPressed"/>
 </LinearLayout>
 
 
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/publisher/res/values/strings.xml
similarity index 82%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/publisher/res/values/strings.xml
index c466993..548c9d4 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/publisher/res/values/strings.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
+    <add-resource type="string" name="publish"/>
     <string name="publish">Publish shortcuts</string>
+
+    <add-resource type="string" name="delete_all"/>
+    <string name="delete_all">Delete all dynamic shortcuts</string>
 </resources>
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/publisher/res1/values/strings.xml
similarity index 87%
copy from samples/ShortcutDemo/res/values/strings.xml
copy to samples/ShortcutDemo/publisher/res1/values/strings.xml
index c466993..aed1197 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/publisher/res1/values/strings.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
+    <string name="app_title">[P1] Shortcuts Demo</string>
 </resources>
diff --git a/samples/ShortcutDemo/res/values/strings.xml b/samples/ShortcutDemo/publisher/res2/values/strings.xml
similarity index 87%
rename from samples/ShortcutDemo/res/values/strings.xml
rename to samples/ShortcutDemo/publisher/res2/values/strings.xml
index c466993..04fdf03 100644
--- a/samples/ShortcutDemo/res/values/strings.xml
+++ b/samples/ShortcutDemo/publisher/res2/values/strings.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_title">Shortcuts Demo</string>
-    <string name="publish">Publish shortcuts</string>
+    <string name="app_title">[P2] Shortcuts Demo</string>
 </resources>
diff --git a/samples/ShortcutDemo/src/com/example/android/pm/shortcuts/IconProvider.java b/samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/IconProvider.java
similarity index 98%
rename from samples/ShortcutDemo/src/com/example/android/pm/shortcuts/IconProvider.java
rename to samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/IconProvider.java
index 83e84bd..e2c5254 100644
--- a/samples/ShortcutDemo/src/com/example/android/pm/shortcuts/IconProvider.java
+++ b/samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/IconProvider.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.example.android.pm.shortcuts;
+package com.example.android.pm.shortcutdemo;
 
 import android.content.ContentProvider;
 import android.content.ContentProvider.PipeDataWriter;
diff --git a/samples/ShortcutDemo/src/com/example/android/pm/shortcuts/Main.java b/samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/Main.java
similarity index 61%
rename from samples/ShortcutDemo/src/com/example/android/pm/shortcuts/Main.java
rename to samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/Main.java
index 68edb82..7a687e8 100644
--- a/samples/ShortcutDemo/src/com/example/android/pm/shortcuts/Main.java
+++ b/samples/ShortcutDemo/publisher/src/com/example/android/pm/shortcutdemo/Main.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.example.android.pm.shortcuts;
+package com.example.android.pm.shortcutdemo;
 
 import android.app.Activity;
 import android.app.WallpaperManager;
@@ -42,10 +42,7 @@
 public class Main extends Activity {
     public static final String TAG = "ShortcutDemo";
 
-    private static final boolean USE_LAUNCHER_APIS = true;
-
     private ShortcutManager mShortcutManager;
-    private LauncherApps mLauncherApps;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -54,23 +51,10 @@
         setContentView(R.layout.main);
 
         mShortcutManager = getSystemService(ShortcutManager.class);
-        mLauncherApps = getSystemService(LauncherApps.class);
-
-        // TODO This will break once LauncherApps implements permission checks.
-        if (USE_LAUNCHER_APIS) {
-            mLauncherApps.registerCallback(mLauncherCallback);
-        }
-
-        WallpaperManager wpm = this.getSystemService(WallpaperManager.class);
-        wpm.getWallpaperFile(WallpaperManager.FLAG_SET_LOCK);
     }
 
     @Override
     protected void onDestroy() {
-        if (USE_LAUNCHER_APIS) {
-            mLauncherApps.unregisterCallback(mLauncherCallback);
-        }
-
         super.onDestroy();
     }
 
@@ -139,62 +123,9 @@
         if (!mShortcutManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3))) {
             showThrottledToast();
         }
-        mLauncherApps.startShortcut(this.getPackageName(), "shortcut1", null, null, Process.myUserHandle());
     }
 
-    private final LauncherApps.Callback mLauncherCallback = new LauncherApps.Callback() {
-        @Override
-        public void onPackageRemoved(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackageAdded(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackageChanged(String packageName, UserHandle user) {
-        }
-
-        @Override
-        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
-        }
-
-        @Override
-        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-                boolean replacing) {
-        }
-
-        @Override
-        public void onShortcutsChanged(String packageName,
-                List<ShortcutInfo> shortcuts, UserHandle user) {
-            Log.w(TAG, "onShortcutsChanged: user=" + user + " package=" + packageName);
-            Log.d(TAG, "Updated shortcuts:");
-            for (ShortcutInfo si : shortcuts) {
-                Log.d(TAG, "  " + si.toString());
-                writeIconToFile(si);
-            }
-        }
-    };
-
-    private void writeIconToFile(ShortcutInfo si) {
-        if (!si.hasIconFile()){
-            return;
-        }
-        String filename = Environment.getExternalStorageDirectory() + "/" + si.getId() + ".png";
-        try (
-                ParcelFileDescriptor pfd = mLauncherApps.getShortcutIconFd(si,
-                    Process.myUserHandle());
-                FileInputStream in = new FileInputStream(pfd.getFileDescriptor());
-                FileOutputStream out = new FileOutputStream(filename)) {
-
-            byte[] buf = new byte[32 * 1024];
-            int len;
-            while ((len = in.read(buf)) >= 0) {
-                out.write(buf, 0, len);
-            }
-            Log.d(TAG, "wrote icon to " + filename);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+    public void onDeleteAllPressed(View view) {
+        mShortcutManager.deleteAllDynamicShortcuts();
     }
 }