Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.SharedPreferences;
     23 import android.content.pm.ActivityInfo;
     24 import android.content.pm.PackageManager;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.util.Base64;
     28 import android.util.Log;
     29 import android.widget.Toast;
     30 
     31 import java.util.ArrayList;
     32 import java.util.HashSet;
     33 import java.util.Iterator;
     34 import java.util.Set;
     35 
     36 import org.json.*;
     37 
     38 public class InstallShortcutReceiver extends BroadcastReceiver {
     39     private static final String TAG = "InstallShortcutReceiver";
     40     private static final boolean DBG = false;
     41 
     42     public static final String ACTION_INSTALL_SHORTCUT =
     43             "com.android.launcher.action.INSTALL_SHORTCUT";
     44 
     45     public static final String DATA_INTENT_KEY = "intent.data";
     46     public static final String LAUNCH_INTENT_KEY = "intent.launch";
     47     public static final String NAME_KEY = "name";
     48     public static final String ICON_KEY = "icon";
     49     public static final String ICON_RESOURCE_NAME_KEY = "iconResource";
     50     public static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
     51     // The set of shortcuts that are pending install
     52     public static final String APPS_PENDING_INSTALL = "apps_to_install";
     53 
     54     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     55     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
     56 
     57     private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
     58     private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
     59 
     60     // A mime-type representing shortcut data
     61     public static final String SHORTCUT_MIMETYPE =
     62             "com.android.launcher3/shortcut";
     63 
     64     private static Object sLock = new Object();
     65 
     66     private static void addToStringSet(SharedPreferences sharedPrefs,
     67             SharedPreferences.Editor editor, String key, String value) {
     68         Set<String> strings = sharedPrefs.getStringSet(key, null);
     69         if (strings == null) {
     70             strings = new HashSet<String>(0);
     71         } else {
     72             strings = new HashSet<String>(strings);
     73         }
     74         strings.add(value);
     75         editor.putStringSet(key, strings);
     76     }
     77 
     78     private static void addToInstallQueue(
     79             SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
     80         synchronized(sLock) {
     81             try {
     82                 JSONStringer json = new JSONStringer()
     83                     .object()
     84                     .key(DATA_INTENT_KEY).value(info.data.toUri(0))
     85                     .key(LAUNCH_INTENT_KEY).value(info.launchIntent.toUri(0))
     86                     .key(NAME_KEY).value(info.name);
     87                 if (info.icon != null) {
     88                     byte[] iconByteArray = ItemInfo.flattenBitmap(info.icon);
     89                     json = json.key(ICON_KEY).value(
     90                         Base64.encodeToString(
     91                             iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
     92                 }
     93                 if (info.iconResource != null) {
     94                     json = json.key(ICON_RESOURCE_NAME_KEY).value(info.iconResource.resourceName);
     95                     json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
     96                         .value(info.iconResource.packageName);
     97                 }
     98                 json = json.endObject();
     99                 SharedPreferences.Editor editor = sharedPrefs.edit();
    100                 if (DBG) Log.d(TAG, "Adding to APPS_PENDING_INSTALL: " + json);
    101                 addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
    102                 editor.commit();
    103             } catch (org.json.JSONException e) {
    104                 Log.d(TAG, "Exception when adding shortcut: " + e);
    105             }
    106         }
    107     }
    108 
    109     public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
    110                                               ArrayList<String> packageNames) {
    111         synchronized(sLock) {
    112             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
    113             if (DBG) {
    114                 Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
    115                         + ", removing packages: " + packageNames);
    116             }
    117             if (strings != null) {
    118                 Set<String> newStrings = new HashSet<String>(strings);
    119                 Iterator<String> newStringsIter = newStrings.iterator();
    120                 while (newStringsIter.hasNext()) {
    121                     String json = newStringsIter.next();
    122                     try {
    123                         JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
    124                         Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
    125                         String pn = launchIntent.getPackage();
    126                         if (pn == null) {
    127                             pn = launchIntent.getComponent().getPackageName();
    128                         }
    129                         if (packageNames.contains(pn)) {
    130                             newStringsIter.remove();
    131                         }
    132                     } catch (org.json.JSONException e) {
    133                         Log.d(TAG, "Exception reading shortcut to remove: " + e);
    134                     } catch (java.net.URISyntaxException e) {
    135                         Log.d(TAG, "Exception reading shortcut to remove: " + e);
    136                     }
    137                 }
    138                 sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
    139                         new HashSet<String>(newStrings)).commit();
    140             }
    141         }
    142     }
    143 
    144     private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
    145             SharedPreferences sharedPrefs) {
    146         synchronized(sLock) {
    147             Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
    148             if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
    149             if (strings == null) {
    150                 return new ArrayList<PendingInstallShortcutInfo>();
    151             }
    152             ArrayList<PendingInstallShortcutInfo> infos =
    153                 new ArrayList<PendingInstallShortcutInfo>();
    154             for (String json : strings) {
    155                 try {
    156                     JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
    157                     Intent data = Intent.parseUri(object.getString(DATA_INTENT_KEY), 0);
    158                     Intent launchIntent =
    159                             Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
    160                     String name = object.getString(NAME_KEY);
    161                     String iconBase64 = object.optString(ICON_KEY);
    162                     String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
    163                     String iconResourcePackageName =
    164                         object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
    165                     if (iconBase64 != null && !iconBase64.isEmpty()) {
    166                         byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
    167                         Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
    168                         data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
    169                     } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
    170                         Intent.ShortcutIconResource iconResource =
    171                             new Intent.ShortcutIconResource();
    172                         iconResource.resourceName = iconResourceName;
    173                         iconResource.packageName = iconResourcePackageName;
    174                         data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
    175                     }
    176                     data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
    177                     PendingInstallShortcutInfo info =
    178                         new PendingInstallShortcutInfo(data, name, launchIntent);
    179                     infos.add(info);
    180                 } catch (org.json.JSONException e) {
    181                     Log.d(TAG, "Exception reading shortcut to add: " + e);
    182                 } catch (java.net.URISyntaxException e) {
    183                     Log.d(TAG, "Exception reading shortcut to add: " + e);
    184                 }
    185             }
    186             sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
    187             return infos;
    188         }
    189     }
    190 
    191     // Determines whether to defer installing shortcuts immediately until
    192     // processAllPendingInstalls() is called.
    193     private static boolean mUseInstallQueue = false;
    194 
    195     private static class PendingInstallShortcutInfo {
    196         Intent data;
    197         Intent launchIntent;
    198         String name;
    199         Bitmap icon;
    200         Intent.ShortcutIconResource iconResource;
    201 
    202         public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
    203                 Intent shortcutIntent) {
    204             data = rawData;
    205             name = shortcutName;
    206             launchIntent = shortcutIntent;
    207         }
    208     }
    209 
    210     public void onReceive(Context context, Intent data) {
    211         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
    212             return;
    213         }
    214 
    215         if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
    216 
    217         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
    218         if (intent == null) {
    219             return;
    220         }
    221         // This name is only used for comparisons and notifications, so fall back to activity name
    222         // if not supplied
    223         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
    224         if (name == null) {
    225             try {
    226                 PackageManager pm = context.getPackageManager();
    227                 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
    228                 name = info.loadLabel(pm).toString();
    229             } catch (PackageManager.NameNotFoundException nnfe) {
    230                 return;
    231             }
    232         }
    233         Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
    234         Intent.ShortcutIconResource iconResource =
    235             data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
    236 
    237         // Queue the item up for adding if launcher has not loaded properly yet
    238         LauncherAppState.setApplicationContext(context.getApplicationContext());
    239         LauncherAppState app = LauncherAppState.getInstance();
    240         boolean launcherNotLoaded = (app.getDynamicGrid() == null);
    241 
    242         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
    243         info.icon = icon;
    244         info.iconResource = iconResource;
    245 
    246         String spKey = LauncherAppState.getSharedPreferencesKey();
    247         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    248         addToInstallQueue(sp, info);
    249         if (!mUseInstallQueue && !launcherNotLoaded) {
    250             flushInstallQueue(context);
    251         }
    252     }
    253 
    254     static void enableInstallQueue() {
    255         mUseInstallQueue = true;
    256     }
    257     static void disableAndFlushInstallQueue(Context context) {
    258         mUseInstallQueue = false;
    259         flushInstallQueue(context);
    260     }
    261     static void flushInstallQueue(Context context) {
    262         String spKey = LauncherAppState.getSharedPreferencesKey();
    263         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    264         ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
    265         if (!installQueue.isEmpty()) {
    266             Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
    267             ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
    268             int result = INSTALL_SHORTCUT_SUCCESSFUL;
    269             String duplicateName = "";
    270             while (iter.hasNext()) {
    271                 final PendingInstallShortcutInfo pendingInfo = iter.next();
    272                 //final Intent data = pendingInfo.data;
    273                 final Intent intent = pendingInfo.launchIntent;
    274                 final String name = pendingInfo.name;
    275                 final boolean exists = LauncherModel.shortcutExists(context, name, intent);
    276                 //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
    277 
    278                 // TODO-XXX: Disable duplicates for now
    279                 if (!exists /* && allowDuplicate */) {
    280                     // Generate a shortcut info to add into the model
    281                     ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
    282                             pendingInfo.launchIntent);
    283                     addShortcuts.add(info);
    284                 }
    285                 /*
    286                 else if (exists && !allowDuplicate) {
    287                     result = INSTALL_SHORTCUT_IS_DUPLICATE;
    288                     duplicateName = name;
    289                 }
    290                 */
    291             }
    292 
    293             // Notify the user once if we weren't able to place any duplicates
    294             if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
    295                 Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
    296                         duplicateName), Toast.LENGTH_SHORT).show();
    297             }
    298 
    299             // Add the new apps to the model and bind them
    300             if (!addShortcuts.isEmpty()) {
    301                 LauncherAppState app = LauncherAppState.getInstance();
    302                 app.getModel().addAndBindAddedApps(context, addShortcuts, null);
    303             }
    304         }
    305     }
    306 
    307     private static ShortcutInfo getShortcutInfo(Context context, Intent data,
    308                                                 Intent launchIntent) {
    309         if (launchIntent.getAction() == null) {
    310             launchIntent.setAction(Intent.ACTION_VIEW);
    311         } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
    312                 launchIntent.getCategories() != null &&
    313                 launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
    314             launchIntent.addFlags(
    315                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    316         }
    317         LauncherAppState app = LauncherAppState.getInstance();
    318         return app.getModel().infoFromShortcutIntent(context, data, null);
    319     }
    320 }
    321