Home | History | Annotate | Download | only in launcher2
      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.launcher2;
     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.widget.Toast;
     26 
     27 import com.android.launcher.R;
     28 
     29 import java.util.ArrayList;
     30 import java.util.HashSet;
     31 import java.util.Iterator;
     32 import java.util.Set;
     33 
     34 public class InstallShortcutReceiver extends BroadcastReceiver {
     35     public static final String ACTION_INSTALL_SHORTCUT =
     36             "com.android.launcher.action.INSTALL_SHORTCUT";
     37     public static final String NEW_APPS_PAGE_KEY = "apps.new.page";
     38     public static final String NEW_APPS_LIST_KEY = "apps.new.list";
     39 
     40     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     41     public static final int NEW_SHORTCUT_STAGGER_DELAY = 75;
     42 
     43     private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
     44     private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
     45     private static final int INSTALL_SHORTCUT_NO_SPACE = -2;
     46 
     47     // A mime-type representing shortcut data
     48     public static final String SHORTCUT_MIMETYPE =
     49             "com.android.launcher/shortcut";
     50 
     51     // The set of shortcuts that are pending install
     52     private static ArrayList<PendingInstallShortcutInfo> mInstallQueue =
     53             new ArrayList<PendingInstallShortcutInfo>();
     54 
     55     // Determines whether to defer installing shortcuts immediately until
     56     // processAllPendingInstalls() is called.
     57     private static boolean mUseInstallQueue = false;
     58 
     59     private static class PendingInstallShortcutInfo {
     60         Intent data;
     61         Intent launchIntent;
     62         String name;
     63 
     64         public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
     65                 Intent shortcutIntent) {
     66             data = rawData;
     67             name = shortcutName;
     68             launchIntent = shortcutIntent;
     69         }
     70     }
     71 
     72     public void onReceive(Context context, Intent data) {
     73         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
     74             return;
     75         }
     76 
     77         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
     78         if (intent == null) {
     79             return;
     80         }
     81         // This name is only used for comparisons and notifications, so fall back to activity name
     82         // if not supplied
     83         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
     84         if (name == null) {
     85             try {
     86                 PackageManager pm = context.getPackageManager();
     87                 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
     88                 name = info.loadLabel(pm).toString();
     89             } catch (PackageManager.NameNotFoundException nnfe) {
     90                 return;
     91             }
     92         }
     93         // Queue the item up for adding if launcher has not loaded properly yet
     94         boolean launcherNotLoaded = LauncherModel.getCellCountX() <= 0 ||
     95                 LauncherModel.getCellCountY() <= 0;
     96 
     97         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
     98         if (mUseInstallQueue || launcherNotLoaded) {
     99             mInstallQueue.add(info);
    100         } else {
    101             processInstallShortcut(context, info);
    102         }
    103     }
    104 
    105     static void enableInstallQueue() {
    106         mUseInstallQueue = true;
    107     }
    108     static void disableAndFlushInstallQueue(Context context) {
    109         mUseInstallQueue = false;
    110         flushInstallQueue(context);
    111     }
    112     static void flushInstallQueue(Context context) {
    113         Iterator<PendingInstallShortcutInfo> iter = mInstallQueue.iterator();
    114         while (iter.hasNext()) {
    115             processInstallShortcut(context, iter.next());
    116             iter.remove();
    117         }
    118     }
    119 
    120     private static void processInstallShortcut(Context context,
    121             PendingInstallShortcutInfo pendingInfo) {
    122         String spKey = LauncherApplication.getSharedPreferencesKey();
    123         SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    124 
    125         final Intent data = pendingInfo.data;
    126         final Intent intent = pendingInfo.launchIntent;
    127         final String name = pendingInfo.name;
    128 
    129         // Lock on the app so that we don't try and get the items while apps are being added
    130         LauncherApplication app = (LauncherApplication) context.getApplicationContext();
    131         final int[] result = {INSTALL_SHORTCUT_SUCCESSFUL};
    132         boolean found = false;
    133         synchronized (app) {
    134             final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
    135             final boolean exists = LauncherModel.shortcutExists(context, name, intent);
    136 
    137             // Try adding to the workspace screens incrementally, starting at the default or center
    138             // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
    139             final int screen = Launcher.DEFAULT_SCREEN;
    140             for (int i = 0; i < (2 * Launcher.SCREEN_COUNT) + 1 && !found; ++i) {
    141                 int si = screen + (int) ((i / 2f) + 0.5f) * ((i % 2 == 1) ? 1 : -1);
    142                 if (0 <= si && si < Launcher.SCREEN_COUNT) {
    143                     found = installShortcut(context, data, items, name, intent, si, exists, sp,
    144                             result);
    145                 }
    146             }
    147         }
    148 
    149         // We only report error messages (duplicate shortcut or out of space) as the add-animation
    150         // will provide feedback otherwise
    151         if (!found) {
    152             if (result[0] == INSTALL_SHORTCUT_NO_SPACE) {
    153                 Toast.makeText(context, context.getString(R.string.completely_out_of_space),
    154                         Toast.LENGTH_SHORT).show();
    155             } else if (result[0] == INSTALL_SHORTCUT_IS_DUPLICATE) {
    156                 Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
    157                         Toast.LENGTH_SHORT).show();
    158             }
    159         }
    160     }
    161 
    162     private static boolean installShortcut(Context context, Intent data, ArrayList<ItemInfo> items,
    163             String name, Intent intent, final int screen, boolean shortcutExists,
    164             final SharedPreferences sharedPrefs, int[] result) {
    165         int[] tmpCoordinates = new int[2];
    166         if (findEmptyCell(context, items, tmpCoordinates, screen)) {
    167             if (intent != null) {
    168                 if (intent.getAction() == null) {
    169                     intent.setAction(Intent.ACTION_VIEW);
    170                 } else if (intent.getAction().equals(Intent.ACTION_MAIN) &&
    171                         intent.getCategories() != null &&
    172                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
    173                     intent.addFlags(
    174                         Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    175                 }
    176 
    177                 // By default, we allow for duplicate entries (located in
    178                 // different places)
    179                 boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
    180                 if (duplicate || !shortcutExists) {
    181                     // If the new app is going to fall into the same page as before, then just
    182                     // continue adding to the current page
    183                     int newAppsScreen = sharedPrefs.getInt(NEW_APPS_PAGE_KEY, screen);
    184                     Set<String> newApps = new HashSet<String>();
    185                     if (newAppsScreen == screen) {
    186                         newApps = sharedPrefs.getStringSet(NEW_APPS_LIST_KEY, newApps);
    187                     }
    188                     synchronized (newApps) {
    189                         newApps.add(intent.toUri(0).toString());
    190                     }
    191                     final Set<String> savedNewApps = newApps;
    192                     new Thread("setNewAppsThread") {
    193                         public void run() {
    194                             synchronized (savedNewApps) {
    195                                 sharedPrefs.edit()
    196                                            .putInt(NEW_APPS_PAGE_KEY, screen)
    197                                            .putStringSet(NEW_APPS_LIST_KEY, savedNewApps)
    198                                            .commit();
    199                             }
    200                         }
    201                     }.start();
    202 
    203                     // Update the Launcher db
    204                     LauncherApplication app = (LauncherApplication) context.getApplicationContext();
    205                     ShortcutInfo info = app.getModel().addShortcut(context, data,
    206                             LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
    207                             tmpCoordinates[0], tmpCoordinates[1], true);
    208                     if (info == null) {
    209                         return false;
    210                     }
    211                 } else {
    212                     result[0] = INSTALL_SHORTCUT_IS_DUPLICATE;
    213                 }
    214 
    215                 return true;
    216             }
    217         } else {
    218             result[0] = INSTALL_SHORTCUT_NO_SPACE;
    219         }
    220 
    221         return false;
    222     }
    223 
    224     private static boolean findEmptyCell(Context context, ArrayList<ItemInfo> items, int[] xy,
    225             int screen) {
    226         final int xCount = LauncherModel.getCellCountX();
    227         final int yCount = LauncherModel.getCellCountY();
    228         boolean[][] occupied = new boolean[xCount][yCount];
    229 
    230         ItemInfo item = null;
    231         int cellX, cellY, spanX, spanY;
    232         for (int i = 0; i < items.size(); ++i) {
    233             item = items.get(i);
    234             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    235                 if (item.screen == screen) {
    236                     cellX = item.cellX;
    237                     cellY = item.cellY;
    238                     spanX = item.spanX;
    239                     spanY = item.spanY;
    240                     for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
    241                         for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
    242                             occupied[x][y] = true;
    243                         }
    244                     }
    245                 }
    246             }
    247         }
    248 
    249         return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
    250     }
    251 }
    252