• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PendingInstallShortcutInfo(Intent rawData, String shortcutName, Intent shortcutIntent)64         public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
65                 Intent shortcutIntent) {
66             data = rawData;
67             name = shortcutName;
68             launchIntent = shortcutIntent;
69         }
70     }
71 
onReceive(Context context, Intent data)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 
enableInstallQueue()105     static void enableInstallQueue() {
106         mUseInstallQueue = true;
107     }
disableAndFlushInstallQueue(Context context)108     static void disableAndFlushInstallQueue(Context context) {
109         mUseInstallQueue = false;
110         flushInstallQueue(context);
111     }
flushInstallQueue(Context context)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 
processInstallShortcut(Context context, PendingInstallShortcutInfo pendingInfo)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 
installShortcut(Context context, Intent data, ArrayList<ItemInfo> items, String name, Intent intent, final int screen, boolean shortcutExists, final SharedPreferences sharedPrefs, int[] result)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 
findEmptyCell(Context context, ArrayList<ItemInfo> items, int[] xy, int screen)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