• 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.launcher;
18 
19 import android.content.ComponentName;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Intent;
23 import android.content.Context;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.res.Resources;
28 import android.database.Cursor;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import static android.util.Log.*;
34 import android.os.Process;
35 
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Comparator;
40 import java.util.concurrent.atomic.AtomicInteger;
41 import java.lang.ref.WeakReference;
42 import java.text.Collator;
43 import java.net.URISyntaxException;
44 
45 /**
46  * Maintains in-memory state of the Launcher. It is expected that there should be only one
47  * LauncherModel object held in a static. Also provide APIs for updating the database state
48  * for the Launcher.
49  */
50 public class LauncherModel {
51     static final boolean DEBUG_LOADERS = true;
52     static final String LOG_TAG = "HomeLoaders";
53 
54     private static final int UI_NOTIFICATION_RATE = 4;
55     private static final int DEFAULT_APPLICATIONS_NUMBER = 42;
56     private static final long APPLICATION_NOT_RESPONDING_TIMEOUT = 5000;
57     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
58 
59     private static final Collator sCollator = Collator.getInstance();
60 
61     private boolean mApplicationsLoaded;
62     private boolean mDesktopItemsLoaded;
63 
64     private ArrayList<ItemInfo> mDesktopItems;
65     private ArrayList<LauncherAppWidgetInfo> mDesktopAppWidgets;
66     private HashMap<Long, FolderInfo> mFolders;
67 
68     private ArrayList<ApplicationInfo> mApplications;
69     private ApplicationsAdapter mApplicationsAdapter;
70     private ApplicationsLoader mApplicationsLoader;
71     private DesktopItemsLoader mDesktopItemsLoader;
72     private Thread mApplicationsLoaderThread;
73     private Thread mDesktopLoaderThread;
74 
75     private final HashMap<ComponentName, ApplicationInfo> mAppInfoCache =
76             new HashMap<ComponentName, ApplicationInfo>(INITIAL_ICON_CACHE_CAPACITY);
77 
abortLoaders()78     synchronized void abortLoaders() {
79         if (DEBUG_LOADERS) d(LOG_TAG, "aborting loaders");
80 
81         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
82             if (DEBUG_LOADERS) d(LOG_TAG, "  --> aborting applications loader");
83             mApplicationsLoader.stop();
84             mApplicationsLoaded = false;
85         }
86 
87         if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) {
88             if (DEBUG_LOADERS) d(LOG_TAG, "  --> aborting workspace loader");
89             mDesktopItemsLoader.stop();
90             mDesktopItemsLoaded = false;
91         }
92     }
93 
94     /**
95      * Drop our cache of components to their lables & icons.  We do
96      * this from Launcher when applications are added/removed.  It's a
97      * bit overkill, but it's a rare operation anyway.
98      */
dropApplicationCache()99     synchronized void dropApplicationCache() {
100         mAppInfoCache.clear();
101     }
102 
103     /**
104      * Loads the list of installed applications in mApplications.
105      *
106      * @return true if the applications loader must be started
107      *         (see startApplicationsLoader()), false otherwise.
108      */
loadApplications(boolean isLaunching, Launcher launcher, boolean localeChanged)109     synchronized boolean loadApplications(boolean isLaunching, Launcher launcher,
110             boolean localeChanged) {
111 
112         if (DEBUG_LOADERS) d(LOG_TAG, "load applications");
113 
114         if (isLaunching && mApplicationsLoaded && !localeChanged) {
115             mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications);
116             if (DEBUG_LOADERS) d(LOG_TAG, "  --> applications loaded, return");
117             return false;
118         }
119 
120         stopAndWaitForApplicationsLoader();
121 
122         if (localeChanged) {
123             dropApplicationCache();
124         }
125 
126         if (mApplicationsAdapter == null || isLaunching || localeChanged) {
127             mApplications = new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
128             mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications);
129         }
130 
131         mApplicationsLoaded = false;
132 
133         if (!isLaunching) {
134             startApplicationsLoaderLocked(launcher, false);
135             return false;
136         }
137 
138         return true;
139     }
140 
stopAndWaitForApplicationsLoader()141     private synchronized void stopAndWaitForApplicationsLoader() {
142         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
143             if (DEBUG_LOADERS) {
144                 d(LOG_TAG, "  --> wait for applications loader (" + mApplicationsLoader.mId + ")");
145             }
146 
147             mApplicationsLoader.stop();
148             // Wait for the currently running thread to finish, this can take a little
149             // time but it should be well below the timeout limit
150             try {
151                 mApplicationsLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT);
152             } catch (InterruptedException e) {
153                 // Empty
154             }
155         }
156     }
157 
startApplicationsLoader(Launcher launcher, boolean isLaunching)158     private synchronized void startApplicationsLoader(Launcher launcher, boolean isLaunching) {
159         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting applications loader unlocked");
160         startApplicationsLoaderLocked(launcher, isLaunching);
161     }
162 
startApplicationsLoaderLocked(Launcher launcher, boolean isLaunching)163     private void startApplicationsLoaderLocked(Launcher launcher, boolean isLaunching) {
164         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting applications loader");
165 
166         stopAndWaitForApplicationsLoader();
167 
168         mApplicationsLoader = new ApplicationsLoader(launcher, isLaunching);
169         mApplicationsLoaderThread = new Thread(mApplicationsLoader, "Applications Loader");
170         mApplicationsLoaderThread.start();
171     }
172 
addPackage(Launcher launcher, String packageName)173     synchronized void addPackage(Launcher launcher, String packageName) {
174         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
175             startApplicationsLoaderLocked(launcher, false);
176             return;
177         }
178 
179         if (packageName != null && packageName.length() > 0) {
180             final PackageManager packageManager = launcher.getPackageManager();
181             final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
182 
183             if (matches.size() > 0) {
184                 final ApplicationsAdapter adapter = mApplicationsAdapter;
185                 final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
186 
187                 for (ResolveInfo info : matches) {
188                     adapter.setNotifyOnChange(false);
189                     adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info, launcher));
190                 }
191 
192                 adapter.sort(new ApplicationInfoComparator());
193                 adapter.notifyDataSetChanged();
194             }
195         }
196     }
197 
removePackage(Launcher launcher, String packageName)198     synchronized void removePackage(Launcher launcher, String packageName) {
199         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
200             dropApplicationCache(); // TODO: this could be optimized
201             startApplicationsLoaderLocked(launcher, false);
202             return;
203         }
204 
205         if (packageName != null && packageName.length() > 0) {
206             final ApplicationsAdapter adapter = mApplicationsAdapter;
207 
208             final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>();
209             final int count = adapter.getCount();
210 
211             for (int i = 0; i < count; i++) {
212                 final ApplicationInfo applicationInfo = adapter.getItem(i);
213                 final Intent intent = applicationInfo.intent;
214                 final ComponentName component = intent.getComponent();
215                 if (packageName.equals(component.getPackageName())) {
216                     toRemove.add(applicationInfo);
217                 }
218             }
219 
220             final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
221             for (ApplicationInfo info : toRemove) {
222                 adapter.setNotifyOnChange(false);
223                 adapter.remove(info);
224                 cache.remove(info.intent.getComponent());
225             }
226 
227             if (toRemove.size() > 0) {
228                 adapter.sort(new ApplicationInfoComparator());
229                 adapter.notifyDataSetChanged();
230             }
231         }
232     }
233 
updatePackage(Launcher launcher, String packageName)234     synchronized void updatePackage(Launcher launcher, String packageName) {
235         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
236             startApplicationsLoaderLocked(launcher, false);
237             return;
238         }
239 
240         if (packageName != null && packageName.length() > 0) {
241             final PackageManager packageManager = launcher.getPackageManager();
242             final ApplicationsAdapter adapter = mApplicationsAdapter;
243 
244             final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
245             final int count = matches.size();
246 
247             boolean changed = false;
248 
249             for (int i = 0; i < count; i++) {
250                 final ResolveInfo info = matches.get(i);
251                 final ApplicationInfo applicationInfo = findIntent(adapter,
252                         info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
253                 if (applicationInfo != null) {
254                     updateAndCacheApplicationInfo(packageManager, info, applicationInfo, launcher);
255                     changed = true;
256                 }
257             }
258 
259             if (syncLocked(launcher, packageName)) changed = true;
260 
261             if (changed) {
262                 adapter.sort(new ApplicationInfoComparator());
263                 adapter.notifyDataSetChanged();
264             }
265         }
266     }
267 
updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info, ApplicationInfo applicationInfo, Context context)268     private void updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info,
269             ApplicationInfo applicationInfo, Context context) {
270 
271         updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo, context);
272 
273         ComponentName componentName = new ComponentName(
274                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
275         mAppInfoCache.put(componentName, applicationInfo);
276     }
277 
syncPackage(Launcher launcher, String packageName)278     synchronized void syncPackage(Launcher launcher, String packageName) {
279         if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) {
280             startApplicationsLoaderLocked(launcher, false);
281             return;
282         }
283 
284         if (packageName != null && packageName.length() > 0) {
285             if (syncLocked(launcher, packageName)) {
286                 final ApplicationsAdapter adapter = mApplicationsAdapter;
287                 adapter.sort(new ApplicationInfoComparator());
288                 adapter.notifyDataSetChanged();
289             }
290         }
291     }
292 
syncLocked(Launcher launcher, String packageName)293     private boolean syncLocked(Launcher launcher, String packageName) {
294         final PackageManager packageManager = launcher.getPackageManager();
295         final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName);
296 
297         if (matches.size() > 0) {
298             final ApplicationsAdapter adapter = mApplicationsAdapter;
299 
300             // Find disabled activities and remove them from the adapter
301             boolean removed = removeDisabledActivities(packageName, matches, adapter);
302             // Find enable activities and add them to the adapter
303             // Also updates existing activities with new labels/icons
304             boolean added = addEnabledAndUpdateActivities(matches, adapter, launcher);
305 
306             return added || removed;
307         }
308 
309         return false;
310     }
311 
findActivitiesForPackage(PackageManager packageManager, String packageName)312     private static List<ResolveInfo> findActivitiesForPackage(PackageManager packageManager,
313             String packageName) {
314 
315         final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
316         mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
317 
318         final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
319         final List<ResolveInfo> matches = new ArrayList<ResolveInfo>();
320 
321         if (apps != null) {
322             // Find all activities that match the packageName
323             int count = apps.size();
324             for (int i = 0; i < count; i++) {
325                 final ResolveInfo info = apps.get(i);
326                 final ActivityInfo activityInfo = info.activityInfo;
327                 if (packageName.equals(activityInfo.packageName)) {
328                     matches.add(info);
329                 }
330             }
331         }
332 
333         return matches;
334     }
335 
addEnabledAndUpdateActivities(List<ResolveInfo> matches, ApplicationsAdapter adapter, Launcher launcher)336     private boolean addEnabledAndUpdateActivities(List<ResolveInfo> matches,
337             ApplicationsAdapter adapter, Launcher launcher) {
338 
339         final List<ApplicationInfo> toAdd = new ArrayList<ApplicationInfo>();
340         final int count = matches.size();
341 
342         boolean changed = false;
343 
344         for (int i = 0; i < count; i++) {
345             final ResolveInfo info = matches.get(i);
346             final ApplicationInfo applicationInfo = findIntent(adapter,
347                     info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
348             if (applicationInfo == null) {
349                 toAdd.add(makeAndCacheApplicationInfo(launcher.getPackageManager(),
350                         mAppInfoCache, info, launcher));
351                 changed = true;
352             } else {
353                 updateAndCacheApplicationInfo(
354                         launcher.getPackageManager(), info, applicationInfo, launcher);
355                 changed = true;
356             }
357         }
358 
359         for (ApplicationInfo info : toAdd) {
360             adapter.setNotifyOnChange(false);
361             adapter.add(info);
362         }
363 
364         return changed;
365     }
366 
removeDisabledActivities(String packageName, List<ResolveInfo> matches, ApplicationsAdapter adapter)367     private boolean removeDisabledActivities(String packageName, List<ResolveInfo> matches,
368             ApplicationsAdapter adapter) {
369 
370         final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>();
371         final int count = adapter.getCount();
372 
373         boolean changed = false;
374 
375         for (int i = 0; i < count; i++) {
376             final ApplicationInfo applicationInfo = adapter.getItem(i);
377             final Intent intent = applicationInfo.intent;
378             final ComponentName component = intent.getComponent();
379             if (packageName.equals(component.getPackageName())) {
380                 if (!findIntent(matches, component)) {
381                     toRemove.add(applicationInfo);
382                     changed = true;
383                 }
384             }
385         }
386 
387         final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache;
388         for (ApplicationInfo info : toRemove) {
389             adapter.setNotifyOnChange(false);
390             adapter.remove(info);
391             cache.remove(info.intent.getComponent());
392         }
393 
394         return changed;
395     }
396 
findIntent(ApplicationsAdapter adapter, String packageName, String name)397     private static ApplicationInfo findIntent(ApplicationsAdapter adapter, String packageName,
398             String name) {
399 
400         final int count = adapter.getCount();
401         for (int i = 0; i < count; i++) {
402             final ApplicationInfo applicationInfo = adapter.getItem(i);
403             final Intent intent = applicationInfo.intent;
404             final ComponentName component = intent.getComponent();
405             if (packageName.equals(component.getPackageName()) &&
406                     name.equals(component.getClassName())) {
407                 return applicationInfo;
408             }
409         }
410 
411         return null;
412     }
413 
findIntent(List<ResolveInfo> apps, ComponentName component)414     private static boolean findIntent(List<ResolveInfo> apps, ComponentName component) {
415         final String className = component.getClassName();
416         for (ResolveInfo info : apps) {
417             final ActivityInfo activityInfo = info.activityInfo;
418             if (activityInfo.name.equals(className)) {
419                 return true;
420             }
421         }
422         return false;
423     }
424 
getApplicationInfoIcon(PackageManager manager, ApplicationInfo info)425     Drawable getApplicationInfoIcon(PackageManager manager, ApplicationInfo info) {
426         final ResolveInfo resolveInfo = manager.resolveActivity(info.intent, 0);
427         if (resolveInfo == null) {
428             return null;
429         }
430 
431         ComponentName componentName = new ComponentName(
432                 resolveInfo.activityInfo.applicationInfo.packageName,
433                 resolveInfo.activityInfo.name);
434         ApplicationInfo application = mAppInfoCache.get(componentName);
435 
436         if (application == null) {
437             return resolveInfo.activityInfo.loadIcon(manager);
438         }
439 
440         return application.icon;
441     }
442 
makeAndCacheApplicationInfo(PackageManager manager, HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info, Context context)443     private static ApplicationInfo makeAndCacheApplicationInfo(PackageManager manager,
444             HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info,
445             Context context) {
446 
447         ComponentName componentName = new ComponentName(
448                 info.activityInfo.applicationInfo.packageName,
449                 info.activityInfo.name);
450         ApplicationInfo application = appInfoCache.get(componentName);
451 
452         if (application == null) {
453             application = new ApplicationInfo();
454             application.container = ItemInfo.NO_ID;
455 
456             updateApplicationInfoTitleAndIcon(manager, info, application, context);
457 
458             application.setActivity(componentName,
459                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
460 
461             appInfoCache.put(componentName, application);
462         }
463 
464         return application;
465     }
466 
updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info, ApplicationInfo application, Context context)467     private static void updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info,
468             ApplicationInfo application, Context context) {
469 
470         application.title = info.loadLabel(manager);
471         if (application.title == null) {
472             application.title = info.activityInfo.name;
473         }
474 
475         application.icon =
476                 Utilities.createIconThumbnail(info.activityInfo.loadIcon(manager), context);
477         application.filtered = false;
478     }
479 
480     private static final AtomicInteger sAppsLoaderCount = new AtomicInteger(1);
481     private static final AtomicInteger sWorkspaceLoaderCount = new AtomicInteger(1);
482 
483     private class ApplicationsLoader implements Runnable {
484         private final WeakReference<Launcher> mLauncher;
485 
486         private volatile boolean mStopped;
487         private volatile boolean mRunning;
488         private final boolean mIsLaunching;
489         private final int mId;
490 
ApplicationsLoader(Launcher launcher, boolean isLaunching)491         ApplicationsLoader(Launcher launcher, boolean isLaunching) {
492             mIsLaunching = isLaunching;
493             mLauncher = new WeakReference<Launcher>(launcher);
494             mRunning = true;
495             mId = sAppsLoaderCount.getAndIncrement();
496         }
497 
stop()498         void stop() {
499             mStopped = true;
500         }
501 
isRunning()502         boolean isRunning() {
503             return mRunning;
504         }
505 
run()506         public void run() {
507             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> running applications loader (" + mId + ")");
508 
509             // Elevate priority when Home launches for the first time to avoid
510             // starving at boot time. Staring at a blank home is not cool.
511             android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT :
512                     Process.THREAD_PRIORITY_BACKGROUND);
513 
514             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
515             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
516 
517             final Launcher launcher = mLauncher.get();
518             final PackageManager manager = launcher.getPackageManager();
519             final List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent, 0);
520 
521             if (apps != null && !mStopped) {
522                 final int count = apps.size();
523                 // Can be set to null on the UI thread by the unbind() method
524                 // Do not access without checking for null first
525                 final ApplicationsAdapter applicationList = mApplicationsAdapter;
526 
527                 ChangeNotifier action = new ChangeNotifier(applicationList, true);
528                 final HashMap<ComponentName, ApplicationInfo> appInfoCache = mAppInfoCache;
529 
530                 for (int i = 0; i < count && !mStopped; i++) {
531                     ResolveInfo info = apps.get(i);
532                     ApplicationInfo application =
533                         makeAndCacheApplicationInfo(manager, appInfoCache, info, launcher);
534 
535                     if (action.add(application) && !mStopped) {
536                         launcher.runOnUiThread(action);
537                         action = new ChangeNotifier(applicationList, false);
538                     }
539                 }
540 
541                 launcher.runOnUiThread(action);
542             }
543 
544             if (!mStopped) {
545                 mApplicationsLoaded = true;
546             } else {
547                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> applications loader stopped (" + mId + ")");
548             }
549             mRunning = false;
550         }
551     }
552 
553     private static class ChangeNotifier implements Runnable {
554         private final ApplicationsAdapter mApplicationList;
555         private final ArrayList<ApplicationInfo> mBuffer;
556 
557         private boolean mFirst = true;
558 
ChangeNotifier(ApplicationsAdapter applicationList, boolean first)559         ChangeNotifier(ApplicationsAdapter applicationList, boolean first) {
560             mApplicationList = applicationList;
561             mFirst = first;
562             mBuffer = new ArrayList<ApplicationInfo>(UI_NOTIFICATION_RATE);
563         }
564 
run()565         public void run() {
566             final ApplicationsAdapter applicationList = mApplicationList;
567             // Can be set to null on the UI thread by the unbind() method
568             if (applicationList == null) return;
569 
570             if (mFirst) {
571                 applicationList.setNotifyOnChange(false);
572                 applicationList.clear();
573                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> cleared application list");
574                 mFirst = false;
575             }
576 
577             final ArrayList<ApplicationInfo> buffer = mBuffer;
578             final int count = buffer.size();
579 
580             for (int i = 0; i < count; i++) {
581                 applicationList.setNotifyOnChange(false);
582                 applicationList.add(buffer.get(i));
583             }
584 
585             buffer.clear();
586 
587             applicationList.sort(new ApplicationInfoComparator());
588             applicationList.notifyDataSetChanged();
589         }
590 
add(ApplicationInfo application)591         boolean add(ApplicationInfo application) {
592             final ArrayList<ApplicationInfo> buffer = mBuffer;
593             buffer.add(application);
594             return buffer.size() >= UI_NOTIFICATION_RATE;
595         }
596     }
597 
598     static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
compare(ApplicationInfo a, ApplicationInfo b)599         public final int compare(ApplicationInfo a, ApplicationInfo b) {
600             return sCollator.compare(a.title.toString(), b.title.toString());
601         }
602     }
603 
isDesktopLoaded()604     boolean isDesktopLoaded() {
605         return mDesktopItems != null && mDesktopAppWidgets != null && mDesktopItemsLoaded;
606     }
607 
608     /**
609      * Loads all of the items on the desktop, in folders, or in the dock.
610      * These can be apps, shortcuts or widgets
611      */
loadUserItems(boolean isLaunching, Launcher launcher, boolean localeChanged, boolean loadApplications)612     void loadUserItems(boolean isLaunching, Launcher launcher, boolean localeChanged,
613             boolean loadApplications) {
614         if (DEBUG_LOADERS) d(LOG_TAG, "loading user items");
615 
616         if (isLaunching && isDesktopLoaded()) {
617             if (DEBUG_LOADERS) d(LOG_TAG, "  --> items loaded, return");
618             if (loadApplications) startApplicationsLoader(launcher, true);
619             // We have already loaded our data from the DB
620             launcher.onDesktopItemsLoaded(mDesktopItems, mDesktopAppWidgets);
621             return;
622         }
623 
624         if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) {
625             if (DEBUG_LOADERS) d(LOG_TAG, "  --> stopping workspace loader");
626             mDesktopItemsLoader.stop();
627             // Wait for the currently running thread to finish, this can take a little
628             // time but it should be well below the timeout limit
629             try {
630                 mDesktopLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT);
631             } catch (InterruptedException e) {
632                 // Empty
633             }
634 
635             // If the thread we are interrupting was tasked to load the list of
636             // applications make sure we keep that information in the new loader
637             // spawned below
638             // note: we don't apply this to localeChanged because the thread can
639             // only be stopped *after* the localeChanged handling has occured
640             loadApplications = mDesktopItemsLoader.mLoadApplications;
641         }
642 
643         if (DEBUG_LOADERS) d(LOG_TAG, "  --> starting workspace loader");
644         mDesktopItemsLoaded = false;
645         mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications,
646                 isLaunching);
647         mDesktopLoaderThread = new Thread(mDesktopItemsLoader, "Desktop Items Loader");
648         mDesktopLoaderThread.start();
649     }
650 
updateShortcutLabels(ContentResolver resolver, PackageManager manager)651     private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
652         final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
653                 new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
654                         LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
655                 null, null, null);
656 
657         final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
658         final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
659         final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
660         final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
661 
662         // boolean changed = false;
663 
664         try {
665             while (c.moveToNext()) {
666                 try {
667                     if (c.getInt(itemTypeIndex) !=
668                             LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
669                         continue;
670                     }
671 
672                     final String intentUri = c.getString(intentIndex);
673                     if (intentUri != null) {
674                         final Intent shortcut = Intent.parseUri(intentUri, 0);
675                         if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
676                             final ComponentName name = shortcut.getComponent();
677                             if (name != null) {
678                                 final ActivityInfo activityInfo = manager.getActivityInfo(name, 0);
679                                 final String title = c.getString(titleIndex);
680                                 String label = getLabel(manager, activityInfo);
681 
682                                 if (title == null || !title.equals(label)) {
683                                     final ContentValues values = new ContentValues();
684                                     values.put(LauncherSettings.Favorites.TITLE, label);
685 
686                                     resolver.update(
687                                             LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
688                                             values, "_id=?",
689                                             new String[] { String.valueOf(c.getLong(idIndex)) });
690 
691                                     // changed = true;
692                                 }
693                             }
694                         }
695                     }
696                 } catch (URISyntaxException e) {
697                     // Ignore
698                 } catch (PackageManager.NameNotFoundException e) {
699                     // Ignore
700                 }
701             }
702         } finally {
703             c.close();
704         }
705 
706         // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null);
707     }
708 
getLabel(PackageManager manager, ActivityInfo activityInfo)709     private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
710         String label = activityInfo.loadLabel(manager).toString();
711         if (label == null) {
712             label = manager.getApplicationLabel(activityInfo.applicationInfo).toString();
713             if (label == null) {
714                 label = activityInfo.name;
715             }
716         }
717         return label;
718     }
719 
720     private class DesktopItemsLoader implements Runnable {
721         private volatile boolean mStopped;
722         private volatile boolean mRunning;
723 
724         private final WeakReference<Launcher> mLauncher;
725         private final boolean mLocaleChanged;
726         private final boolean mLoadApplications;
727         private final boolean mIsLaunching;
728         private final int mId;
729 
DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications, boolean isLaunching)730         DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications,
731                 boolean isLaunching) {
732             mLoadApplications = loadApplications;
733             mIsLaunching = isLaunching;
734             mLauncher = new WeakReference<Launcher>(launcher);
735             mLocaleChanged = localeChanged;
736             mId = sWorkspaceLoaderCount.getAndIncrement();
737         }
738 
stop()739         void stop() {
740             mStopped = true;
741         }
742 
isRunning()743         boolean isRunning() {
744             return mRunning;
745         }
746 
run()747         public void run() {
748             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> running workspace loader (" + mId + ")");
749 
750             mRunning = true;
751 
752             android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
753 
754             final Launcher launcher = mLauncher.get();
755             final ContentResolver contentResolver = launcher.getContentResolver();
756             final PackageManager manager = launcher.getPackageManager();
757 
758             if (mLocaleChanged) {
759                 updateShortcutLabels(contentResolver, manager);
760             }
761 
762             mDesktopItems = new ArrayList<ItemInfo>();
763             mDesktopAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
764             mFolders = new HashMap<Long, FolderInfo>();
765 
766             final ArrayList<ItemInfo> desktopItems = mDesktopItems;
767             final ArrayList<LauncherAppWidgetInfo> desktopAppWidgets = mDesktopAppWidgets;
768 
769             final Cursor c = contentResolver.query(
770                     LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
771 
772             try {
773                 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
774                 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
775                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
776                 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
777                 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
778                 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
779                 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
780                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
781                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
782                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID);
783                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
784                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
785                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
786                 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
787                 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
788                 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
789                 final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
790 
791                 ApplicationInfo info;
792                 String intentDescription;
793                 Widget widgetInfo;
794                 LauncherAppWidgetInfo appWidgetInfo;
795                 int container;
796                 long id;
797                 Intent intent;
798 
799                 final HashMap<Long, FolderInfo> folders = mFolders;
800 
801                 while (!mStopped && c.moveToNext()) {
802                     try {
803                         int itemType = c.getInt(itemTypeIndex);
804 
805                         switch (itemType) {
806                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
807                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
808                             intentDescription = c.getString(intentIndex);
809                             try {
810                                 intent = Intent.parseUri(intentDescription, 0);
811                             } catch (java.net.URISyntaxException e) {
812                                 continue;
813                             }
814 
815                             if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
816                                 info = getApplicationInfo(manager, intent, launcher);
817                             } else {
818                                 info = getApplicationInfoShortcut(c, launcher, iconTypeIndex,
819                                         iconPackageIndex, iconResourceIndex, iconIndex);
820                             }
821 
822                             if (info == null) {
823                                 info = new ApplicationInfo();
824                                 info.icon = manager.getDefaultActivityIcon();
825                             }
826 
827                             if (info != null) {
828                                 info.title = c.getString(titleIndex);
829                                 info.intent = intent;
830 
831                                 info.id = c.getLong(idIndex);
832                                 container = c.getInt(containerIndex);
833                                 info.container = container;
834                                 info.screen = c.getInt(screenIndex);
835                                 info.cellX = c.getInt(cellXIndex);
836                                 info.cellY = c.getInt(cellYIndex);
837 
838                                 switch (container) {
839                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
840                                     desktopItems.add(info);
841                                     break;
842                                 default:
843                                     // Item is in a user folder
844                                     UserFolderInfo folderInfo =
845                                             findOrMakeUserFolder(folders, container);
846                                     folderInfo.add(info);
847                                     break;
848                                 }
849                             }
850                             break;
851                         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
852 
853                             id = c.getLong(idIndex);
854                             UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id);
855 
856                             folderInfo.title = c.getString(titleIndex);
857 
858                             folderInfo.id = id;
859                             container = c.getInt(containerIndex);
860                             folderInfo.container = container;
861                             folderInfo.screen = c.getInt(screenIndex);
862                             folderInfo.cellX = c.getInt(cellXIndex);
863                             folderInfo.cellY = c.getInt(cellYIndex);
864 
865                             switch (container) {
866                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
867                                     desktopItems.add(folderInfo);
868                                     break;
869                             }
870                             break;
871                         case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
872 
873                             id = c.getLong(idIndex);
874                             LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id);
875 
876                             intentDescription = c.getString(intentIndex);
877                             intent = null;
878                             if (intentDescription != null) {
879                                 try {
880                                     intent = Intent.parseUri(intentDescription, 0);
881                                 } catch (java.net.URISyntaxException e) {
882                                     // Ignore, a live folder might not have a base intent
883                                 }
884                             }
885 
886                             liveFolderInfo.title = c.getString(titleIndex);
887                             liveFolderInfo.id = id;
888                             container = c.getInt(containerIndex);
889                             liveFolderInfo.container = container;
890                             liveFolderInfo.screen = c.getInt(screenIndex);
891                             liveFolderInfo.cellX = c.getInt(cellXIndex);
892                             liveFolderInfo.cellY = c.getInt(cellYIndex);
893                             liveFolderInfo.uri = Uri.parse(c.getString(uriIndex));
894                             liveFolderInfo.baseIntent = intent;
895                             liveFolderInfo.displayMode = c.getInt(displayModeIndex);
896 
897                             loadLiveFolderIcon(launcher, c, iconTypeIndex, iconPackageIndex,
898                                     iconResourceIndex, liveFolderInfo);
899 
900                             switch (container) {
901                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
902                                     desktopItems.add(liveFolderInfo);
903                                     break;
904                             }
905                             break;
906                         case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
907                             widgetInfo = Widget.makeSearch();
908 
909                             container = c.getInt(containerIndex);
910                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
911                                 e(Launcher.LOG_TAG, "Widget found where container "
912                                         + "!= CONTAINER_DESKTOP  ignoring!");
913                                 continue;
914                             }
915 
916                             widgetInfo.id = c.getLong(idIndex);
917                             widgetInfo.screen = c.getInt(screenIndex);
918                             widgetInfo.container = container;
919                             widgetInfo.cellX = c.getInt(cellXIndex);
920                             widgetInfo.cellY = c.getInt(cellYIndex);
921 
922                             desktopItems.add(widgetInfo);
923                             break;
924                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
925                             // Read all Launcher-specific widget details
926                             int appWidgetId = c.getInt(appWidgetIdIndex);
927                             appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
928                             appWidgetInfo.id = c.getLong(idIndex);
929                             appWidgetInfo.screen = c.getInt(screenIndex);
930                             appWidgetInfo.cellX = c.getInt(cellXIndex);
931                             appWidgetInfo.cellY = c.getInt(cellYIndex);
932                             appWidgetInfo.spanX = c.getInt(spanXIndex);
933                             appWidgetInfo.spanY = c.getInt(spanYIndex);
934 
935                             container = c.getInt(containerIndex);
936                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
937                                 e(Launcher.LOG_TAG, "Widget found where container "
938                                         + "!= CONTAINER_DESKTOP -- ignoring!");
939                                 continue;
940                             }
941                             appWidgetInfo.container = c.getInt(containerIndex);
942 
943                             desktopAppWidgets.add(appWidgetInfo);
944                             break;
945                         }
946                     } catch (Exception e) {
947                         w(Launcher.LOG_TAG, "Desktop items loading interrupted:", e);
948                     }
949                 }
950             } finally {
951                 c.close();
952             }
953 
954             if (!mStopped) {
955                 if (DEBUG_LOADERS)  {
956                     d(LOG_TAG, "  --> done loading workspace");
957                     d(LOG_TAG, "  ----> worskpace items=" + desktopItems.size());
958                     d(LOG_TAG, "  ----> worskpace widgets=" + desktopAppWidgets.size());
959                 }
960 
961                 // Create a copy of the lists in case the workspace loader is restarted
962                 // and the list are cleared before the UI can go through them
963                 final ArrayList<ItemInfo> uiDesktopItems =
964                         new ArrayList<ItemInfo>(desktopItems);
965                 final ArrayList<LauncherAppWidgetInfo> uiDesktopWidgets =
966                         new ArrayList<LauncherAppWidgetInfo>(desktopAppWidgets);
967 
968                 if (!mStopped) {
969                     d(LOG_TAG, "  ----> items cloned, ready to refresh UI");
970                     launcher.runOnUiThread(new Runnable() {
971                         public void run() {
972                             if (DEBUG_LOADERS) d(LOG_TAG, "  ----> onDesktopItemsLoaded()");
973                             launcher.onDesktopItemsLoaded(uiDesktopItems, uiDesktopWidgets);
974                         }
975                     });
976                 }
977 
978                 if (mLoadApplications) {
979                     if (DEBUG_LOADERS) {
980                         d(LOG_TAG, "  ----> loading applications from workspace loader");
981                     }
982                     startApplicationsLoader(launcher, mIsLaunching);
983                 }
984 
985                 mDesktopItemsLoaded = true;
986             } else {
987                 if (DEBUG_LOADERS) d(LOG_TAG, "  ----> worskpace loader was stopped");
988             }
989             mRunning = false;
990         }
991     }
992 
loadLiveFolderIcon(Launcher launcher, Cursor c, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo)993     private static void loadLiveFolderIcon(Launcher launcher, Cursor c, int iconTypeIndex,
994             int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
995 
996         int iconType = c.getInt(iconTypeIndex);
997         switch (iconType) {
998             case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
999                 String packageName = c.getString(iconPackageIndex);
1000                 String resourceName = c.getString(iconResourceIndex);
1001                 PackageManager packageManager = launcher.getPackageManager();
1002                 try {
1003                     Resources resources = packageManager.getResourcesForApplication(packageName);
1004                     final int id = resources.getIdentifier(resourceName, null, null);
1005                     liveFolderInfo.icon = resources.getDrawable(id);
1006                 } catch (Exception e) {
1007                     liveFolderInfo.icon =
1008                             launcher.getResources().getDrawable(R.drawable.ic_launcher_folder);
1009                 }
1010                 liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
1011                 liveFolderInfo.iconResource.packageName = packageName;
1012                 liveFolderInfo.iconResource.resourceName = resourceName;
1013                 break;
1014             default:
1015                 liveFolderInfo.icon =
1016                         launcher.getResources().getDrawable(R.drawable.ic_launcher_folder);
1017         }
1018     }
1019 
1020     /**
1021      * Finds the user folder defined by the specified id.
1022      *
1023      * @param id The id of the folder to look for.
1024      *
1025      * @return A UserFolderInfo if the folder exists or null otherwise.
1026      */
findFolderById(long id)1027     FolderInfo findFolderById(long id) {
1028         return mFolders.get(id);
1029     }
1030 
addFolder(FolderInfo info)1031     void addFolder(FolderInfo info) {
1032         mFolders.put(info.id, info);
1033     }
1034 
1035     /**
1036      * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1037      * new one.
1038      */
findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id)1039     private UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) {
1040         // See if a placeholder was created for us already
1041         FolderInfo folderInfo = folders.get(id);
1042         if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
1043             // No placeholder -- create a new instance
1044             folderInfo = new UserFolderInfo();
1045             folders.put(id, folderInfo);
1046         }
1047         return (UserFolderInfo) folderInfo;
1048     }
1049 
1050     /**
1051      * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1052      * new one.
1053      */
findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id)1054     private LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) {
1055         // See if a placeholder was created for us already
1056         FolderInfo folderInfo = folders.get(id);
1057         if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
1058             // No placeholder -- create a new instance
1059             folderInfo = new LiveFolderInfo();
1060             folders.put(id, folderInfo);
1061         }
1062         return (LiveFolderInfo) folderInfo;
1063     }
1064 
1065     /**
1066      * Remove the callback for the cached drawables or we leak the previous
1067      * Home screen on orientation change.
1068      */
unbind()1069     void unbind() {
1070         // Interrupt the applications loader before setting the adapter to null
1071         stopAndWaitForApplicationsLoader();
1072         mApplicationsAdapter = null;
1073         unbindAppDrawables(mApplications);
1074         unbindDrawables(mDesktopItems);
1075         unbindAppWidgetHostViews(mDesktopAppWidgets);
1076         unbindCachedIconDrawables();
1077     }
1078 
1079     /**
1080      * Remove the callback for the cached drawables or we leak the previous
1081      * Home screen on orientation change.
1082      */
unbindDrawables(ArrayList<ItemInfo> desktopItems)1083     private void unbindDrawables(ArrayList<ItemInfo> desktopItems) {
1084         if (desktopItems != null) {
1085             final int count = desktopItems.size();
1086             for (int i = 0; i < count; i++) {
1087                 ItemInfo item = desktopItems.get(i);
1088                 switch (item.itemType) {
1089                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1090                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1091                     ((ApplicationInfo)item).icon.setCallback(null);
1092                     break;
1093                 }
1094             }
1095         }
1096     }
1097 
1098     /**
1099      * Remove the callback for the cached drawables or we leak the previous
1100      * Home screen on orientation change.
1101      */
unbindAppDrawables(ArrayList<ApplicationInfo> applications)1102     private void unbindAppDrawables(ArrayList<ApplicationInfo> applications) {
1103         if (applications != null) {
1104             final int count = applications.size();
1105             for (int i = 0; i < count; i++) {
1106                 applications.get(i).icon.setCallback(null);
1107             }
1108         }
1109     }
1110 
1111     /**
1112      * Remove any {@link LauncherAppWidgetHostView} references in our widgets.
1113      */
unbindAppWidgetHostViews(ArrayList<LauncherAppWidgetInfo> appWidgets)1114     private void unbindAppWidgetHostViews(ArrayList<LauncherAppWidgetInfo> appWidgets) {
1115         if (appWidgets != null) {
1116             final int count = appWidgets.size();
1117             for (int i = 0; i < count; i++) {
1118                 LauncherAppWidgetInfo launcherInfo = appWidgets.get(i);
1119                 launcherInfo.hostView = null;
1120             }
1121         }
1122     }
1123 
1124     /**
1125      * Remove the callback for the cached drawables or we leak the previous
1126      * Home screen on orientation change.
1127      */
unbindCachedIconDrawables()1128     private void unbindCachedIconDrawables() {
1129         for (ApplicationInfo appInfo : mAppInfoCache.values()) {
1130             appInfo.icon.setCallback(null);
1131         }
1132     }
1133 
1134     /**
1135      * @return The current list of applications
1136      */
getApplicationsAdapter()1137     ApplicationsAdapter getApplicationsAdapter() {
1138         return mApplicationsAdapter;
1139     }
1140 
1141     /**
1142      * @return The current list of desktop items
1143      */
getDesktopItems()1144     ArrayList<ItemInfo> getDesktopItems() {
1145         return mDesktopItems;
1146     }
1147 
1148     /**
1149      * @return The current list of desktop items
1150      */
getDesktopAppWidgets()1151     ArrayList<LauncherAppWidgetInfo> getDesktopAppWidgets() {
1152         return mDesktopAppWidgets;
1153     }
1154 
1155     /**
1156      * Add an item to the desktop
1157      * @param info
1158      */
addDesktopItem(ItemInfo info)1159     void addDesktopItem(ItemInfo info) {
1160         // TODO: write to DB; also check that folder has been added to folders list
1161         mDesktopItems.add(info);
1162     }
1163 
1164     /**
1165      * Remove an item from the desktop
1166      * @param info
1167      */
removeDesktopItem(ItemInfo info)1168     void removeDesktopItem(ItemInfo info) {
1169         // TODO: write to DB; figure out if we should remove folder from folders list
1170         mDesktopItems.remove(info);
1171     }
1172 
1173     /**
1174      * Add a widget to the desktop
1175      */
addDesktopAppWidget(LauncherAppWidgetInfo info)1176     void addDesktopAppWidget(LauncherAppWidgetInfo info) {
1177         mDesktopAppWidgets.add(info);
1178     }
1179 
1180     /**
1181      * Remove a widget from the desktop
1182      */
removeDesktopAppWidget(LauncherAppWidgetInfo info)1183     void removeDesktopAppWidget(LauncherAppWidgetInfo info) {
1184         mDesktopAppWidgets.remove(info);
1185     }
1186 
1187     /**
1188      * Make an ApplicationInfo object for an application
1189      */
getApplicationInfo(PackageManager manager, Intent intent, Context context)1190     private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
1191                                                       Context context) {
1192         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
1193 
1194         if (resolveInfo == null) {
1195             return null;
1196         }
1197 
1198         final ApplicationInfo info = new ApplicationInfo();
1199         final ActivityInfo activityInfo = resolveInfo.activityInfo;
1200         info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
1201         if (info.title == null || info.title.length() == 0) {
1202             info.title = activityInfo.loadLabel(manager);
1203         }
1204         if (info.title == null) {
1205             info.title = "";
1206         }
1207         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
1208         return info;
1209     }
1210 
1211     /**
1212      * Make an ApplicationInfo object for a sortcut
1213      */
getApplicationInfoShortcut(Cursor c, Context context, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex)1214     private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
1215             int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
1216 
1217         final ApplicationInfo info = new ApplicationInfo();
1218         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1219 
1220         int iconType = c.getInt(iconTypeIndex);
1221         switch (iconType) {
1222             case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
1223                 String packageName = c.getString(iconPackageIndex);
1224                 String resourceName = c.getString(iconResourceIndex);
1225                 PackageManager packageManager = context.getPackageManager();
1226                 try {
1227                     Resources resources = packageManager.getResourcesForApplication(packageName);
1228                     final int id = resources.getIdentifier(resourceName, null, null);
1229                     info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
1230                 } catch (Exception e) {
1231                     info.icon = packageManager.getDefaultActivityIcon();
1232                 }
1233                 info.iconResource = new Intent.ShortcutIconResource();
1234                 info.iconResource.packageName = packageName;
1235                 info.iconResource.resourceName = resourceName;
1236                 info.customIcon = false;
1237                 break;
1238             case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
1239                 byte[] data = c.getBlob(iconIndex);
1240                 try {
1241                     Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
1242                     info.icon = new FastBitmapDrawable(
1243                             Utilities.createBitmapThumbnail(bitmap, context));
1244                 } catch (Exception e) {
1245                     packageManager = context.getPackageManager();
1246                     info.icon = packageManager.getDefaultActivityIcon();
1247                 }
1248                 info.filtered = true;
1249                 info.customIcon = true;
1250                 break;
1251             default:
1252                 info.icon = context.getPackageManager().getDefaultActivityIcon();
1253                 info.customIcon = false;
1254                 break;
1255         }
1256         return info;
1257     }
1258 
1259     /**
1260      * Remove an item from the in-memory represention of a user folder. Does not change the DB.
1261      */
removeUserFolderItem(UserFolderInfo folder, ItemInfo info)1262     void removeUserFolderItem(UserFolderInfo folder, ItemInfo info) {
1263         //noinspection SuspiciousMethodCalls
1264         folder.contents.remove(info);
1265     }
1266 
1267     /**
1268      * Removes a UserFolder from the in-memory list of folders. Does not change the DB.
1269      * @param userFolderInfo
1270      */
removeUserFolder(UserFolderInfo userFolderInfo)1271     void removeUserFolder(UserFolderInfo userFolderInfo) {
1272         mFolders.remove(userFolderInfo.id);
1273     }
1274 
1275     /**
1276      * Adds an item to the DB if it was not created previously, or move it to a new
1277      * <container, screen, cellX, cellY>
1278      */
addOrMoveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY)1279     static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
1280             int screen, int cellX, int cellY) {
1281         if (item.container == ItemInfo.NO_ID) {
1282             // From all apps
1283             addItemToDatabase(context, item, container, screen, cellX, cellY, false);
1284         } else {
1285             // From somewhere else
1286             moveItemInDatabase(context, item, container, screen, cellX, cellY);
1287         }
1288     }
1289 
1290     /**
1291      * Move an item in the DB to a new <container, screen, cellX, cellY>
1292      */
moveItemInDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY)1293     static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
1294             int cellX, int cellY) {
1295         item.container = container;
1296         item.screen = screen;
1297         item.cellX = cellX;
1298         item.cellY = cellY;
1299 
1300         final ContentValues values = new ContentValues();
1301         final ContentResolver cr = context.getContentResolver();
1302 
1303         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
1304         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
1305         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
1306         values.put(LauncherSettings.Favorites.SCREEN, item.screen);
1307 
1308         cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
1309     }
1310 
1311     /**
1312      * Returns true if the shortcuts already exists in the database.
1313      * we identify a shortcut by its title and intent.
1314      */
shortcutExists(Context context, String title, Intent intent)1315     static boolean shortcutExists(Context context, String title, Intent intent) {
1316         final ContentResolver cr = context.getContentResolver();
1317         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
1318             new String[] { "title", "intent" }, "title=? and intent=?",
1319             new String[] { title, intent.toUri(0) }, null);
1320         boolean result = false;
1321         try {
1322             result = c.moveToFirst();
1323         } finally {
1324             c.close();
1325         }
1326         return result;
1327     }
1328 
getFolderById(Context context, long id)1329     FolderInfo getFolderById(Context context, long id) {
1330         final ContentResolver cr = context.getContentResolver();
1331         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
1332                 "_id=? and (itemType=? or itemType=?)",
1333                 new String[] { String.valueOf(id),
1334                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER),
1335                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null);
1336 
1337         try {
1338             if (c.moveToFirst()) {
1339                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1340                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1341                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1342                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1343                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1344                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1345 
1346                 FolderInfo folderInfo = null;
1347                 switch (c.getInt(itemTypeIndex)) {
1348                     case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1349                         folderInfo = findOrMakeUserFolder(mFolders, id);
1350                         break;
1351                     case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
1352                         folderInfo = findOrMakeLiveFolder(mFolders, id);
1353                         break;
1354                 }
1355 
1356                 folderInfo.title = c.getString(titleIndex);
1357                 folderInfo.id = id;
1358                 folderInfo.container = c.getInt(containerIndex);
1359                 folderInfo.screen = c.getInt(screenIndex);
1360                 folderInfo.cellX = c.getInt(cellXIndex);
1361                 folderInfo.cellY = c.getInt(cellYIndex);
1362 
1363                 return folderInfo;
1364             }
1365         } finally {
1366             c.close();
1367         }
1368 
1369         return null;
1370     }
1371 
1372     /**
1373      * Add an item to the database in a specified container. Sets the container, screen, cellX and
1374      * cellY fields of the item. Also assigns an ID to the item.
1375      */
addItemToDatabase(Context context, ItemInfo item, long container, int screen, int cellX, int cellY, boolean notify)1376     static void addItemToDatabase(Context context, ItemInfo item, long container,
1377             int screen, int cellX, int cellY, boolean notify) {
1378         item.container = container;
1379         item.screen = screen;
1380         item.cellX = cellX;
1381         item.cellY = cellY;
1382 
1383         final ContentValues values = new ContentValues();
1384         final ContentResolver cr = context.getContentResolver();
1385 
1386         item.onAddToDatabase(values);
1387 
1388         Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1389                 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1390 
1391         if (result != null) {
1392             item.id = Integer.parseInt(result.getPathSegments().get(1));
1393         }
1394     }
1395 
1396     /**
1397      * Add an item to the database in a specified container. Sets the container, screen, cellX and
1398      * cellY fields of the item. Also assigns an ID to the item.
1399      */
addGestureToDatabase(Context context, ItemInfo item, boolean notify)1400     static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) {
1401         final ContentValues values = new ContentValues();
1402         final ContentResolver cr = context.getContentResolver();
1403 
1404         item.onAddToDatabase(values);
1405 
1406         Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI :
1407                 LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values);
1408 
1409         if (result != null) {
1410             item.id = Integer.parseInt(result.getPathSegments().get(1));
1411         }
1412 
1413         return result != null;
1414     }
1415 
1416     /**
1417      * Update an item to the database in a specified container.
1418      */
updateItemInDatabase(Context context, ItemInfo item)1419     static void updateItemInDatabase(Context context, ItemInfo item) {
1420         final ContentValues values = new ContentValues();
1421         final ContentResolver cr = context.getContentResolver();
1422 
1423         item.onAddToDatabase(values);
1424 
1425         cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
1426     }
1427 
1428     /**
1429      * Removes the specified item from the database
1430      * @param context
1431      * @param item
1432      */
deleteItemFromDatabase(Context context, ItemInfo item)1433     static void deleteItemFromDatabase(Context context, ItemInfo item) {
1434         final ContentResolver cr = context.getContentResolver();
1435 
1436         cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null);
1437     }
1438 
1439 
1440     /**
1441      * Remove the contents of the specified folder from the database
1442      */
deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info)1443     static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) {
1444         final ContentResolver cr = context.getContentResolver();
1445 
1446         cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1447         cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1448                 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1449     }
1450 
deleteGestureFromDatabase(Context context, ItemInfo item)1451     static void deleteGestureFromDatabase(Context context, ItemInfo item) {
1452         final ContentResolver cr = context.getContentResolver();
1453 
1454         cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null);
1455     }
1456 
updateGestureInDatabase(Context context, ItemInfo item)1457     static void updateGestureInDatabase(Context context, ItemInfo item) {
1458         final ContentValues values = new ContentValues();
1459         final ContentResolver cr = context.getContentResolver();
1460 
1461         item.onAddToDatabase(values);
1462 
1463         cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null);
1464     }
1465 
1466 
queryGesture(Context context, String id)1467     ApplicationInfo queryGesture(Context context, String id) {
1468         final ContentResolver contentResolver = context.getContentResolver();
1469         final PackageManager manager = context.getPackageManager();
1470         final Cursor c = contentResolver.query(
1471                 LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?",
1472                 new String[] { id }, null);
1473 
1474         ApplicationInfo info = null;
1475 
1476         try {
1477             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID);
1478             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT);
1479             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE);
1480             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE);
1481             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON);
1482             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE);
1483             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE);
1484             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE);
1485 
1486             String intentDescription;
1487             Intent intent;
1488 
1489             if (c.moveToNext()) {
1490                 int itemType = c.getInt(itemTypeIndex);
1491 
1492                 switch (itemType) {
1493                     case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1494                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1495                         intentDescription = c.getString(intentIndex);
1496                         try {
1497                             intent = Intent.parseUri(intentDescription, 0);
1498                         } catch (java.net.URISyntaxException e) {
1499                             return null;
1500                         }
1501 
1502                         if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1503                             info = getApplicationInfo(manager, intent, context);
1504                         } else {
1505                             info = getApplicationInfoShortcut(c, context, iconTypeIndex,
1506                                     iconPackageIndex, iconResourceIndex, iconIndex);
1507                         }
1508 
1509                         if (info == null) {
1510                             info = new ApplicationInfo();
1511                             info.icon = manager.getDefaultActivityIcon();
1512                         }
1513 
1514                         info.isGesture = true;
1515                         info.title = c.getString(titleIndex);
1516                         info.intent = intent;
1517                         info.id = c.getLong(idIndex);
1518 
1519                         break;
1520                 }
1521             }
1522         } catch (Exception e) {
1523             w(LOG_TAG, "Could not load gesture with name " + id);
1524         } finally {
1525             c.close();
1526         }
1527 
1528         return info;
1529     }
1530 }
1531