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