InstallShortcutReceiver.java revision dff0bfeb901e767ca9b57f9b8051c7fd077626e9
131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project/*
231dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
331dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project *
431dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
531dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * you may not use this file except in compliance with the License.
631dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * You may obtain a copy of the License at
731dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project *
831dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
931dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project *
1031dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1231dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1331dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * See the License for the specific language governing permissions and
1431dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project * limitations under the License.
1531dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project */
1631dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project
17325dc23624160689e59fbac708cf6f222b20d025Daniel Sandlerpackage com.android.launcher3;
1831dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project
1931dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Projectimport android.content.BroadcastReceiver;
2031dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Projectimport android.content.Context;
2131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Projectimport android.content.Intent;
22f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chungimport android.content.SharedPreferences;
23c3a747ac245d6175a6838230d8b01c618555e062Winson Chungimport android.content.pm.ActivityInfo;
24c3a747ac245d6175a6838230d8b01c618555e062Winson Chungimport android.content.pm.PackageManager;
2548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurkaimport android.graphics.Bitmap;
2648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurkaimport android.graphics.BitmapFactory;
27dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawalimport android.text.TextUtils;
2848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurkaimport android.util.Base64;
2948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurkaimport android.util.Log;
30d93a7d1716b06cd65f45dfc16232277d0d57c04dRomain Guyimport android.widget.Toast;
3131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project
3234c2e6cf7af328aba25c98158161dbad15ae986dMichael Jurkaimport org.json.JSONObject;
3334c2e6cf7af328aba25c98158161dbad15ae986dMichael Jurkaimport org.json.JSONStringer;
3434c2e6cf7af328aba25c98158161dbad15ae986dMichael Jurkaimport org.json.JSONTokener;
3534c2e6cf7af328aba25c98158161dbad15ae986dMichael Jurka
36e428e292c27e650ab4de76b78728ba55019cd282Winson Chungimport java.util.ArrayList;
37f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chungimport java.util.HashSet;
38f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chungimport java.util.Iterator;
39f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chungimport java.util.Set;
40e428e292c27e650ab4de76b78728ba55019cd282Winson Chung
4131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Projectpublic class InstallShortcutReceiver extends BroadcastReceiver {
424e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert    private static final String TAG = "InstallShortcutReceiver";
434e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert    private static final boolean DBG = false;
444e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert
45a9abd0e0bdedb5cbbd12b84cb83037a735e79a20Winson Chung    public static final String ACTION_INSTALL_SHORTCUT =
4694d6768c65929efa21bb893fdef7f269d65da3c3Winson Chung            "com.android.launcher.action.INSTALL_SHORTCUT";
47f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung
4848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String DATA_INTENT_KEY = "intent.data";
4948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String LAUNCH_INTENT_KEY = "intent.launch";
5048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String NAME_KEY = "name";
5148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String ICON_KEY = "icon";
5248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String ICON_RESOURCE_NAME_KEY = "iconResource";
5348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
5448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    // The set of shortcuts that are pending install
5548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    public static final String APPS_PENDING_INSTALL = "apps_to_install";
5648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka
57f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
58997a92348a6d6e061737399321393449c16cd4d8Winson Chung    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
59f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung
60f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung    private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
61f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung    private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
6251ed5b9bc05c6eb37dcb3fe38fa021f1044c0f88Romain Guy
6368846fdce6c01bbe474bd0c8307e1ccaac161cbcWinson Chung    // A mime-type representing shortcut data
6468846fdce6c01bbe474bd0c8307e1ccaac161cbcWinson Chung    public static final String SHORTCUT_MIMETYPE =
65325dc23624160689e59fbac708cf6f222b20d025Daniel Sandler            "com.android.launcher3/shortcut";
6668846fdce6c01bbe474bd0c8307e1ccaac161cbcWinson Chung
67c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka    private static Object sLock = new Object();
68c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka
69c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka    private static void addToStringSet(SharedPreferences sharedPrefs,
70c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka            SharedPreferences.Editor editor, String key, String value) {
71c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        Set<String> strings = sharedPrefs.getStringSet(key, null);
72c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        if (strings == null) {
73c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka            strings = new HashSet<String>(0);
74c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        } else {
75c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka            strings = new HashSet<String>(strings);
76c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        }
77c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        strings.add(value);
78c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka        editor.putStringSet(key, strings);
79c7700af21f7b1df939fce22f4ab5072b3ea29b66Michael Jurka    }
8048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka
8148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    private static void addToInstallQueue(
8248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
8348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        synchronized(sLock) {
8448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            try {
8548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                JSONStringer json = new JSONStringer()
8648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    .object()
8748c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    .key(DATA_INTENT_KEY).value(info.data.toUri(0))
8848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    .key(LAUNCH_INTENT_KEY).value(info.launchIntent.toUri(0))
8948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    .key(NAME_KEY).value(info.name);
9048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                if (info.icon != null) {
9148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    byte[] iconByteArray = ItemInfo.flattenBitmap(info.icon);
9248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    json = json.key(ICON_KEY).value(
9348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        Base64.encodeToString(
9448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                            iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
9548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                }
9648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                if (info.iconResource != null) {
9748c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    json = json.key(ICON_RESOURCE_NAME_KEY).value(info.iconResource.resourceName);
9848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
9948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        .value(info.iconResource.packageName);
10048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                }
10148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                json = json.endObject();
10248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                SharedPreferences.Editor editor = sharedPrefs.edit();
1034e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                if (DBG) Log.d(TAG, "Adding to APPS_PENDING_INSTALL: " + json);
10448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
10548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                editor.commit();
10648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            } catch (org.json.JSONException e) {
1074e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                Log.d(TAG, "Exception when adding shortcut: " + e);
10848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            }
10948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        }
11048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    }
11148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka
112780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung    public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
113780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                                              ArrayList<String> packageNames) {
114df95eb12a4d288ffa143a0c3db60e7e98f3e03b9Winson Chung        if (packageNames.isEmpty()) {
115df95eb12a4d288ffa143a0c3db60e7e98f3e03b9Winson Chung            return;
116df95eb12a4d288ffa143a0c3db60e7e98f3e03b9Winson Chung        }
117780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung        synchronized(sLock) {
118780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
1194e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert            if (DBG) {
1204e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
1214e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                        + ", removing packages: " + packageNames);
1224e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert            }
123780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung            if (strings != null) {
124780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                Set<String> newStrings = new HashSet<String>(strings);
1254e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                Iterator<String> newStringsIter = newStrings.iterator();
1264e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                while (newStringsIter.hasNext()) {
1274e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                    String json = newStringsIter.next();
128780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                    try {
129780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
130780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
131780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        String pn = launchIntent.getPackage();
132780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        if (pn == null) {
133780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                            pn = launchIntent.getComponent().getPackageName();
134780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        }
135780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        if (packageNames.contains(pn)) {
1364e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                            newStringsIter.remove();
137780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        }
138780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                    } catch (org.json.JSONException e) {
1394e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
140780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                    } catch (java.net.URISyntaxException e) {
1414e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                        Log.d(TAG, "Exception reading shortcut to remove: " + e);
142780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                    }
143780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                }
144780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
145780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                        new HashSet<String>(newStrings)).commit();
146780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung            }
147780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung        }
148780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung    }
149780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung
15048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
15148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            SharedPreferences sharedPrefs) {
15248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        synchronized(sLock) {
15348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
1544e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert            if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
15548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            if (strings == null) {
15648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                return new ArrayList<PendingInstallShortcutInfo>();
15748c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            }
15848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            ArrayList<PendingInstallShortcutInfo> infos =
15948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                new ArrayList<PendingInstallShortcutInfo>();
16048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            for (String json : strings) {
16148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                try {
16248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
16348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    Intent data = Intent.parseUri(object.getString(DATA_INTENT_KEY), 0);
164780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                    Intent launchIntent =
165780fe59a7af8b12fbdcd7f6841edaa7f2c2e019dWinson Chung                            Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
16648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    String name = object.getString(NAME_KEY);
16748c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    String iconBase64 = object.optString(ICON_KEY);
16848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
16948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    String iconResourcePackageName =
17048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
17148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    if (iconBase64 != null && !iconBase64.isEmpty()) {
17248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
17348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
17448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
17548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
17648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        Intent.ShortcutIconResource iconResource =
17748c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                            new Intent.ShortcutIconResource();
17848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        iconResource.resourceName = iconResourceName;
17948c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        iconResource.packageName = iconResourcePackageName;
18048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
18148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    }
18248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
18348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    PendingInstallShortcutInfo info =
18448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                        new PendingInstallShortcutInfo(data, name, launchIntent);
18548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                    infos.add(info);
18648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                } catch (org.json.JSONException e) {
1874e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                    Log.d(TAG, "Exception reading shortcut to add: " + e);
18848c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                } catch (java.net.URISyntaxException e) {
1894e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert                    Log.d(TAG, "Exception reading shortcut to add: " + e);
19048c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka                }
19148c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            }
19248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
19348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            return infos;
19448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        }
19548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka    }
19648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka
197f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    // Determines whether to defer installing shortcuts immediately until
198f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    // processAllPendingInstalls() is called.
199f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    private static boolean mUseInstallQueue = false;
200f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung
201f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    private static class PendingInstallShortcutInfo {
202f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        Intent data;
203f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        Intent launchIntent;
204f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        String name;
20548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        Bitmap icon;
20648c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        Intent.ShortcutIconResource iconResource;
207f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung
208f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
209f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung                Intent shortcutIntent) {
210f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung            data = rawData;
211f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung            name = shortcutName;
212f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung            launchIntent = shortcutIntent;
213f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        }
214f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    }
21531dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project
21631dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project    public void onReceive(Context context, Intent data) {
21751ed5b9bc05c6eb37dcb3fe38fa021f1044c0f88Romain Guy        if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
21851ed5b9bc05c6eb37dcb3fe38fa021f1044c0f88Romain Guy            return;
21951ed5b9bc05c6eb37dcb3fe38fa021f1044c0f88Romain Guy        }
22051ed5b9bc05c6eb37dcb3fe38fa021f1044c0f88Romain Guy
2214e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert        if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
2224e871a217b286775dc6c07feb6d9f1d7a7e914beBjorn Bringert
223f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
224c3a747ac245d6175a6838230d8b01c618555e062Winson Chung        if (intent == null) {
225c3a747ac245d6175a6838230d8b01c618555e062Winson Chung            return;
226c3a747ac245d6175a6838230d8b01c618555e062Winson Chung        }
227dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal
228c3a747ac245d6175a6838230d8b01c618555e062Winson Chung        // This name is only used for comparisons and notifications, so fall back to activity name
229c3a747ac245d6175a6838230d8b01c618555e062Winson Chung        // if not supplied
2302c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        String name = ensureValidName(context, intent,
2312c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung                data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)).toString();
23248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
23348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        Intent.ShortcutIconResource iconResource =
23448c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka            data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
23548c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka
236de0fb8fbf11caac382589ab464b50d528942273aWinson Chung        // Queue the item up for adding if launcher has not loaded properly yet
237c88dceb0b567ab96418e73cf2f6b197ba2646cb3Winson Chung        LauncherAppState.setApplicationContext(context.getApplicationContext());
238892c74d460ad98c6306420e1127c9aa3e505ba25Winson Chung        LauncherAppState app = LauncherAppState.getInstance();
239892c74d460ad98c6306420e1127c9aa3e505ba25Winson Chung        boolean launcherNotLoaded = (app.getDynamicGrid() == null);
240b7bea81c7a83994c694b6ea643a3edad29cce3f3Winson Chung
241f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
24248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        info.icon = icon;
24348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        info.iconResource = iconResource;
244997a92348a6d6e061737399321393449c16cd4d8Winson Chung
245997a92348a6d6e061737399321393449c16cd4d8Winson Chung        String spKey = LauncherAppState.getSharedPreferencesKey();
246997a92348a6d6e061737399321393449c16cd4d8Winson Chung        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
247997a92348a6d6e061737399321393449c16cd4d8Winson Chung        addToInstallQueue(sp, info);
248997a92348a6d6e061737399321393449c16cd4d8Winson Chung        if (!mUseInstallQueue && !launcherNotLoaded) {
249997a92348a6d6e061737399321393449c16cd4d8Winson Chung            flushInstallQueue(context);
250f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        }
251f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    }
252f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung
253f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    static void enableInstallQueue() {
254f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        mUseInstallQueue = true;
255f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    }
256f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung    static void disableAndFlushInstallQueue(Context context) {
257f561bdf68dd304e1d7c3f7796621a4cc39ca15f2Winson Chung        mUseInstallQueue = false;
258de0fb8fbf11caac382589ab464b50d528942273aWinson Chung        flushInstallQueue(context);
259de0fb8fbf11caac382589ab464b50d528942273aWinson Chung    }
260de0fb8fbf11caac382589ab464b50d528942273aWinson Chung    static void flushInstallQueue(Context context) {
261cc8befac3550c81d04ea206c170adae36c9a6c93Daniel Sandler        String spKey = LauncherAppState.getSharedPreferencesKey();
26248c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
26348c7a93eb7ef15ef551aee80c179b8e7e0d2399aMichael Jurka        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
264997a92348a6d6e061737399321393449c16cd4d8Winson Chung        if (!installQueue.isEmpty()) {
265997a92348a6d6e061737399321393449c16cd4d8Winson Chung            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
266997a92348a6d6e061737399321393449c16cd4d8Winson Chung            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
267997a92348a6d6e061737399321393449c16cd4d8Winson Chung            int result = INSTALL_SHORTCUT_SUCCESSFUL;
268997a92348a6d6e061737399321393449c16cd4d8Winson Chung            String duplicateName = "";
269997a92348a6d6e061737399321393449c16cd4d8Winson Chung            while (iter.hasNext()) {
270997a92348a6d6e061737399321393449c16cd4d8Winson Chung                final PendingInstallShortcutInfo pendingInfo = iter.next();
27126acafbe67565a9cc6aeb7d6c43a1c6d1cb9b73cMichael Jurka                //final Intent data = pendingInfo.data;
272997a92348a6d6e061737399321393449c16cd4d8Winson Chung                final Intent intent = pendingInfo.launchIntent;
273997a92348a6d6e061737399321393449c16cd4d8Winson Chung                final String name = pendingInfo.name;
274dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal
275dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                if (AppsCustomizePagedView.DISABLE_ALL_APPS && !isValidShortcutLaunchIntent(intent)) {
276dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                    if (DBG) Log.d(TAG, "Ignoring shortcut with launchIntent:" + intent);
277dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                    continue;
278dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                }
279dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal
280997a92348a6d6e061737399321393449c16cd4d8Winson Chung                final boolean exists = LauncherModel.shortcutExists(context, name, intent);
28126acafbe67565a9cc6aeb7d6c43a1c6d1cb9b73cMichael Jurka                //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
282997a92348a6d6e061737399321393449c16cd4d8Winson Chung
283997a92348a6d6e061737399321393449c16cd4d8Winson Chung                // TODO-XXX: Disable duplicates for now
284997a92348a6d6e061737399321393449c16cd4d8Winson Chung                if (!exists /* && allowDuplicate */) {
285997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    // Generate a shortcut info to add into the model
286997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
287997a92348a6d6e061737399321393449c16cd4d8Winson Chung                            pendingInfo.launchIntent);
288997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    addShortcuts.add(info);
289997a92348a6d6e061737399321393449c16cd4d8Winson Chung                }
290997a92348a6d6e061737399321393449c16cd4d8Winson Chung                /*
291997a92348a6d6e061737399321393449c16cd4d8Winson Chung                else if (exists && !allowDuplicate) {
292997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    result = INSTALL_SHORTCUT_IS_DUPLICATE;
293997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    duplicateName = name;
294a2413751e3a698aef9c87411a639637883856939Winson Chung                }
295997a92348a6d6e061737399321393449c16cd4d8Winson Chung                */
29631dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project            }
297e428e292c27e650ab4de76b78728ba55019cd282Winson Chung
298997a92348a6d6e061737399321393449c16cd4d8Winson Chung            // Notify the user once if we weren't able to place any duplicates
299997a92348a6d6e061737399321393449c16cd4d8Winson Chung            if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
300997a92348a6d6e061737399321393449c16cd4d8Winson Chung                Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
301997a92348a6d6e061737399321393449c16cd4d8Winson Chung                        duplicateName), Toast.LENGTH_SHORT).show();
302f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung            }
303f0c6ae0e35d3e020db55b5b826955da66b14b7f6Winson Chung
304997a92348a6d6e061737399321393449c16cd4d8Winson Chung            // Add the new apps to the model and bind them
305997a92348a6d6e061737399321393449c16cd4d8Winson Chung            if (!addShortcuts.isEmpty()) {
306997a92348a6d6e061737399321393449c16cd4d8Winson Chung                LauncherAppState app = LauncherAppState.getInstance();
307fe9d96a39651de99819208e95c7ec7d432a402e0Winson Chung                app.getModel().addAndBindAddedApps(context, addShortcuts, new ArrayList<AppInfo>());
30831dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project            }
30931dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project        }
31031dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project    }
31131dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project
312dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal    /**
313dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * Returns true if the intent is a valid launch intent for a shortcut.
314dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * This is used to identify shortcuts which are different from the ones exposed by the
315dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * applications' manifest file.
316dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     *
317dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * When DISABLE_ALL_APPS is true, shortcuts exposed via the app's manifest should never be
318dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * duplicated or removed(unless the app is un-installed).
319dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     *
320dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     * @param launchIntent The intent that will be launched when the shortcut is clicked.
321dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal     */
322dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal    static boolean isValidShortcutLaunchIntent(Intent launchIntent) {
323dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal        if (launchIntent != null
324dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && Intent.ACTION_MAIN.equals(launchIntent.getAction())
325dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && launchIntent.getComponent() != null
326dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && launchIntent.getCategories() != null
327dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && launchIntent.getCategories().size() == 1
328dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
329dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && launchIntent.getExtras() == null
330dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal                && TextUtils.isEmpty(launchIntent.getDataString())) {
331dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal            return false;
332dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal        }
333dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal        return true;
334dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal    }
335dff0bfeb901e767ca9b57f9b8051c7fd077626e9Nilesh Agrawal
336997a92348a6d6e061737399321393449c16cd4d8Winson Chung    private static ShortcutInfo getShortcutInfo(Context context, Intent data,
337997a92348a6d6e061737399321393449c16cd4d8Winson Chung                                                Intent launchIntent) {
338997a92348a6d6e061737399321393449c16cd4d8Winson Chung        if (launchIntent.getAction() == null) {
339997a92348a6d6e061737399321393449c16cd4d8Winson Chung            launchIntent.setAction(Intent.ACTION_VIEW);
340997a92348a6d6e061737399321393449c16cd4d8Winson Chung        } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
341997a92348a6d6e061737399321393449c16cd4d8Winson Chung                launchIntent.getCategories() != null &&
342997a92348a6d6e061737399321393449c16cd4d8Winson Chung                launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
343997a92348a6d6e061737399321393449c16cd4d8Winson Chung            launchIntent.addFlags(
344997a92348a6d6e061737399321393449c16cd4d8Winson Chung                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
34531dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project        }
346997a92348a6d6e061737399321393449c16cd4d8Winson Chung        LauncherAppState app = LauncherAppState.getInstance();
3472c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        ShortcutInfo info = app.getModel().infoFromShortcutIntent(context, data, null);
3482c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        info.title = ensureValidName(context, launchIntent, info.title);
3492c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        return info;
3502c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung    }
3512c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung
3522c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung    /**
3532c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung     * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
3542c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung     * the application name instead.
3552c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung     */
3562c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung    private static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
3572c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        if (name == null) {
3582c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung            try {
3592c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung                PackageManager pm = context.getPackageManager();
3602c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
3612c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung                name = info.loadLabel(pm).toString();
3622c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung            } catch (PackageManager.NameNotFoundException nnfe) {
3632c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung                return "";
3642c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung            }
3652c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        }
3662c1afde5cd60eb13f05aeae0c170e697493ef307Winson Chung        return name;
36731dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project    }
36831dd503c6aa69018e694d91724d46db49ea09327The Android Open Source Project}
369