diff options
author | Joe Onorato <joeo@android.com> | 2009-08-17 11:03:03 -0400 |
---|---|---|
committer | Joe Onorato <joeo@android.com> | 2009-08-24 14:58:44 -0400 |
commit | 9c1289cb3bfb74f86e53ec7ac6dd76bb39666b2d (patch) | |
tree | bb6f3ea57e53ccf695bd7cf2e3db33786506be5c /src | |
parent | 02e638e6bf6dac903396a67029d4d48e3cfa87d9 (diff) | |
download | packages_apps_trebuchet-9c1289cb3bfb74f86e53ec7ac6dd76bb39666b2d.zip packages_apps_trebuchet-9c1289cb3bfb74f86e53ec7ac6dd76bb39666b2d.tar.gz packages_apps_trebuchet-9c1289cb3bfb74f86e53ec7ac6dd76bb39666b2d.tar.bz2 |
Redo the launcher loading code and put the real app icons into rollo.
Diffstat (limited to 'src')
21 files changed, 1922 insertions, 1845 deletions
diff --git a/src/com/android/launcher2/ActivityPicker.java b/src/com/android/launcher2/ActivityPicker.java index f502e27..b6da08e 100644 --- a/src/com/android/launcher2/ActivityPicker.java +++ b/src/com/android/launcher2/ActivityPicker.java @@ -402,4 +402,4 @@ public class ActivityPicker extends AlertActivity implements return icon; } } -}
\ No newline at end of file +} diff --git a/src/com/android/launcher2/AllAppsList.java b/src/com/android/launcher2/AllAppsList.java new file mode 100644 index 0000000..4d3ee77 --- /dev/null +++ b/src/com/android/launcher2/AllAppsList.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Intent; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.Log; +import android.os.Process; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +/** + * Stores the list of all applications for the all apps view. + */ +class AllAppsList { + public static final int DEFAULT_APPLICATIONS_NUMBER = 42; + + /** The list off all apps. */ + public ArrayList<ApplicationInfo> data = new ArrayList(DEFAULT_APPLICATIONS_NUMBER); + /** The list of apps that have been added since the last notify() call. */ + public ArrayList<ApplicationInfo> added = new ArrayList(DEFAULT_APPLICATIONS_NUMBER); + /** The list of apps that have been removed since the last notify() call. */ + public ArrayList<ApplicationInfo> removed = new ArrayList(); + /** The list of apps that have been modified since the last notify() call. */ + public ArrayList<ApplicationInfo> modified = new ArrayList(); + + /** + * Boring constructor. + */ + public AllAppsList() { + } + + /** + * Add the supplied ApplicationInfo objects to the list, and enqueue it into the + * list to broadcast when notify() is called. + */ + public void add(ApplicationInfo info) { + data.add(info); + added.add(info); + } + + public void clear() { + data.clear(); + // TODO: do we clear these too? + added.clear(); + removed.clear(); + modified.clear(); + } + + public int size() { + return data.size(); + } + + public ApplicationInfo get(int index) { + return data.get(index); + } + + /** + * Add the icons for the supplied apk called packageName. + */ + public void addPackage(Context context, String packageName) { + final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName); + + if (matches.size() > 0) { + Utilities.BubbleText bubble = new Utilities.BubbleText(context); + for (ResolveInfo info : matches) { + ApplicationInfo item = AppInfoCache.cache(info, context, bubble); + data.add(item); + added.add(item); + } + } + } + + /** + * Remove the apps for the given apk identified by packageName. + */ + public void removePackage(String packageName) { + final List<ApplicationInfo> data = this.data; + for (int i=data.size()-1; i>=0; i--) { + ApplicationInfo info = data.get(i); + final ComponentName component = info.intent.getComponent(); + if (packageName.equals(component.getPackageName())) { + removed.add(info); + data.remove(i); + } + } + // This is more aggressive than it needs to be. + AppInfoCache.flush(); + } + + /** + * Add and remove icons for this package which has been updated. + */ + public void updatePackage(Context context, String packageName) { + final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName); + if (matches.size() > 0) { + // Find disabled/removed activities and remove them from data and add them + // to the removed list. + for (int i=data.size()-1; i>=0; i--) { + final ApplicationInfo applicationInfo = data.get(i); + final ComponentName component = applicationInfo.intent.getComponent(); + if (packageName.equals(component.getPackageName())) { + if (!findActivity(matches, component)) { + removed.add(applicationInfo); + AppInfoCache.remove(component); + data.remove(i); + } + } + } + + // Find enabled activities and add them to the adapter + // Also updates existing activities with new labels/icons + Utilities.BubbleText bubble = new Utilities.BubbleText(context); + int count = matches.size(); + for (int i=0; i<count; i++) { + final ResolveInfo info = matches.get(i); + ApplicationInfo applicationInfo = findApplicationInfoLocked( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + if (applicationInfo == null) { + applicationInfo = AppInfoCache.cache(info, context, bubble); + data.add(applicationInfo); + added.add(applicationInfo); + } else { + AppInfoCache.update(info, applicationInfo, context); + modified.add(applicationInfo); + } + } + } + } + + /** + * Query the package manager for MAIN/LAUNCHER activities in the supplied package. + */ + private static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) { + final PackageManager packageManager = context.getPackageManager(); + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); + final List<ResolveInfo> matches = new ArrayList<ResolveInfo>(); + + if (apps != null) { + // Find all activities that match the packageName + int count = apps.size(); + for (int i = 0; i < count; i++) { + final ResolveInfo info = apps.get(i); + final ActivityInfo activityInfo = info.activityInfo; + if (packageName.equals(activityInfo.packageName)) { + matches.add(info); + } + } + } + + return matches; + } + + /** + * Returns whether <em>apps</em> contains <em>component</em>. + */ + private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) { + final String className = component.getClassName(); + for (ResolveInfo info : apps) { + final ActivityInfo activityInfo = info.activityInfo; + if (activityInfo.name.equals(className)) { + return true; + } + } + return false; + } + + /** + * Find an ApplicationInfo object for the given packageName and className. + */ + private ApplicationInfo findApplicationInfoLocked(String packageName, String className) { + for (ApplicationInfo info: data) { + final ComponentName component = info.intent.getComponent(); + if (packageName.equals(component.getPackageName()) + && className.equals(component.getClassName())) { + return info; + } + } + return null; + } +} diff --git a/src/com/android/launcher2/AllAppsView.java b/src/com/android/launcher2/AllAppsView.java index 755ba65..dbb7564 100644 --- a/src/com/android/launcher2/AllAppsView.java +++ b/src/com/android/launcher2/AllAppsView.java @@ -59,8 +59,11 @@ import android.graphics.PixelFormat; public class AllAppsView extends RSSurfaceView { + private static final String TAG = "Launcher.AllAppsView"; + private RenderScript mRS; private RolloRS mRollo; + private ArrayList<ApplicationInfo> mAllAppsList; private ViewConfiguration mConfig; private VelocityTracker mVelocity; @@ -97,6 +100,9 @@ public class AllAppsView extends RSSurfaceView { mRS = createRenderScript(); mRollo = new RolloRS(); mRollo.init(getResources(), w, h); + if (mAllAppsList != null) { + mRollo.setApps(mAllAppsList); + } } @Override @@ -109,7 +115,6 @@ public class AllAppsView extends RSSurfaceView { @Override public boolean onTouchEvent(MotionEvent ev) { - Log.d(Launcher.LOG_TAG, "onTouchEvent " + ev); int x = (int)ev.getX(); int deltaX; switch (ev.getAction()) { @@ -163,10 +168,17 @@ public class AllAppsView extends RSSurfaceView { DataSetObserver mIconObserver = new DataSetObserver() { public void onChanged() { - Log.d(Launcher.LOG_TAG, "new icons arrived! now have " + mAdapter.getCount()); + Log.d(TAG, "new icons arrived! now have " + mAdapter.getCount()); } }; + public void setApps(ArrayList<ApplicationInfo> list) { + mAllAppsList = list; + if (mRollo != null) { + mRollo.setApps(list); + } + } + public class RolloRS { // Allocations ====== @@ -316,22 +328,9 @@ public class AllAppsView extends RSSurfaceView { } private void initData() { - final int count = 100; mParams = new Params(mRS); mState = new State(mRS); - mIcons = new Allocation[count]; - mAllocIconIDBuf = new int[count]; - mAllocIconID = Allocation.createSized(mRS, - Element.USER_I32, mAllocIconIDBuf.length); - - mLabels = new Allocation[count]; - mAllocLabelIDBuf = new int[mLabels.length]; - mAllocLabelID = Allocation.createSized(mRS, - Element.USER_I32, mLabels.length); - - Element ie8888 = Element.RGBA_8888; - final Utilities.BubbleText bubble = new Utilities.BubbleText(getContext()); mParams.bubbleWidth = bubble.getBubbleWidth(); @@ -339,25 +338,10 @@ public class AllAppsView extends RSSurfaceView { mParams.bubbleBitmapWidth = bubble.getBitmapWidth(); mParams.bubbleBitmapHeight = bubble.getBitmapHeight(); - for (int i=0; i<count; i++) { - mIcons[i] = Allocation.createFromBitmapResource( - mRS, mRes, R.raw.maps, ie8888, true); - mLabels[i] = makeTextBitmap(bubble, i%9==0 ? "Google Maps" : "Maps"); - } - - for (int i=0; i<count; i++) { - mIcons[i].uploadToTexture(0); - mLabels[i].uploadToTexture(0); - mAllocIconIDBuf[i] = mIcons[i].getID(); - mAllocLabelIDBuf[i] = mLabels[i].getID(); - } - mAllocIconID.data(mAllocIconIDBuf); - mAllocLabelID.data(mAllocLabelIDBuf); - - mState.iconCount = count; - mParams.save(); mState.save(); + + setApps(null); } Allocation makeTextBitmap(Utilities.BubbleText bubble, String label) { @@ -383,8 +367,51 @@ public class AllAppsView extends RSSurfaceView { mRS.contextBindRootScript(mScript); } - } + private void setApps(ArrayList<ApplicationInfo> list) { + final int count = list != null ? list.size() : 0; + mIcons = new Allocation[count]; + mAllocIconIDBuf = new int[count]; + mAllocIconID = Allocation.createSized(mRS, Element.USER_I32, count); + + mLabels = new Allocation[count]; + mAllocLabelIDBuf = new int[count]; + mAllocLabelID = Allocation.createSized(mRS, Element.USER_I32, count); + + Element ie8888 = Element.RGBA_8888; + + Utilities.BubbleText bubble = new Utilities.BubbleText(getContext()); + + for (int i=0; i<count; i++) { + final ApplicationInfo item = list.get(i); + + mIcons[i] = Allocation.createFromBitmap(mRS, item.iconBitmap, + Element.RGBA_8888, true); + mLabels[i] = Allocation.createFromBitmap(mRS, item.titleBitmap, + Element.RGBA_8888, true); + + mIcons[i].uploadToTexture(0); + mLabels[i].uploadToTexture(0); + + mAllocIconIDBuf[i] = mIcons[i].getID(); + mAllocLabelIDBuf[i] = mLabels[i].getID(); + } + + mAllocIconID.data(mAllocIconIDBuf); + mAllocLabelID.data(mAllocLabelIDBuf); + + mState.iconCount = count; + + Log.d("AllAppsView", "mScript=" + mScript + " mAllocIconID=" + mAllocIconID); + + if (mScript != null) { // wtf + mScript.bindAllocation(mAllocIconID, Defines.ALLOC_ICON_IDS); + mScript.bindAllocation(mAllocLabelID, Defines.ALLOC_LABEL_IDS); + } + + mState.save(); + } + } } diff --git a/src/com/android/launcher2/AppInfoCache.java b/src/com/android/launcher2/AppInfoCache.java new file mode 100644 index 0000000..34e843e --- /dev/null +++ b/src/com/android/launcher2/AppInfoCache.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Intent; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.util.Log; +import android.os.Process; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Cache of application icons. Icons can be made from any thread. + */ +public class AppInfoCache { + private static final String TAG = "Launcher.AppInfoCache"; + + private static final int INITIAL_ICON_CACHE_CAPACITY = 50; + + private static final HashMap<ComponentName, ApplicationInfo> sCache = + new HashMap<ComponentName, ApplicationInfo>(INITIAL_ICON_CACHE_CAPACITY); + + /** + * no public constructor. + */ + private AppInfoCache() { + } + + /** + * For the given ResolveInfo, return an ApplicationInfo and cache the result for later. + */ + public static ApplicationInfo cache(ResolveInfo info, Context context, + Utilities.BubbleText bubble) { + synchronized (sCache) { + ComponentName componentName = new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + ApplicationInfo application = sCache.get(componentName); + + if (application == null) { + application = new ApplicationInfo(); + application.container = ItemInfo.NO_ID; + + updateTitleAndIcon(info, application, context, bubble); + + application.setActivity(componentName, + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + sCache.put(componentName, application); + } + + return application; + } + } + + /** + * Update the entry in the in the cache with its new metadata. + */ + public static void update(ResolveInfo info, ApplicationInfo applicationInfo, Context context) { + synchronized (sCache) { + updateTitleAndIcon(info, applicationInfo, context, new Utilities.BubbleText(context)); + + ComponentName componentName = new ComponentName( + info.activityInfo.applicationInfo.packageName, info.activityInfo.name); + sCache.put(componentName, applicationInfo); + } + } + + /** + * Remove any records for the supplied ComponentName. + */ + public static void remove(ComponentName componentName) { + synchronized (sCache) { + sCache.remove(componentName); + } + } + + /** + * Empty out the cache. + */ + public static void flush() { + synchronized (sCache) { + sCache.clear(); + } + } + + /** + * Get the icon for the supplied ApplicationInfo. If that activity already + * exists in the cache, use that. + */ + public static Drawable getIconDrawable(PackageManager packageManager, ApplicationInfo info) { + final ResolveInfo resolveInfo = packageManager.resolveActivity(info.intent, 0); + if (resolveInfo == null) { + return null; + } + + ComponentName componentName = new ComponentName( + resolveInfo.activityInfo.applicationInfo.packageName, + resolveInfo.activityInfo.name); + ApplicationInfo cached; + synchronized (sCache) { + cached = sCache.get(componentName); + } + + if (cached != null) { + return cached.icon; + } else { + return resolveInfo.activityInfo.loadIcon(packageManager); + } + } + + /** + * Go through the cache and disconnect any of the callbacks in the drawables or we + * leak the previous Home screen on orientation change. + */ + public static void unbindDrawables() { + synchronized (sCache) { + for (ApplicationInfo appInfo: sCache.values()) { + appInfo.icon.setCallback(null); + } + } + } + + /** + * Update the title and icon. Don't keep a reference to the context! + */ + private static void updateTitleAndIcon(ResolveInfo info, ApplicationInfo application, + Context context, Utilities.BubbleText bubble) { + final PackageManager packageManager = context.getPackageManager(); + + application.title = info.loadLabel(packageManager); + if (application.title == null) { + application.title = info.activityInfo.name; + } + + Drawable icon = Utilities.createIconThumbnail(info.activityInfo.loadIcon(packageManager), + context, true); + application.iconBitmap = ((FastBitmapDrawable)icon).getBitmap(); + application.filtered = true; + + application.titleBitmap = bubble.createTextBitmap(application.title.toString()); + } +} + diff --git a/src/com/android/launcher2/ApplicationInfo.java b/src/com/android/launcher2/ApplicationInfo.java index 912f04e..db802c7 100644 --- a/src/com/android/launcher2/ApplicationInfo.java +++ b/src/com/android/launcher2/ApplicationInfo.java @@ -34,6 +34,11 @@ class ApplicationInfo extends ItemInfo { CharSequence title; /** + * A bitmap of the application's text in the bubble. + */ + Bitmap titleBitmap; + + /** * The intent used to start the application. */ Intent intent; @@ -44,6 +49,11 @@ class ApplicationInfo extends ItemInfo { Drawable icon; /** + * A bitmap version of the application icon. + */ + Bitmap iconBitmap; + + /** * When set to true, indicates that the icon has been resized. */ boolean filtered; @@ -124,4 +134,10 @@ class ApplicationInfo extends ItemInfo { public String toString() { return title.toString(); } + + @Override + void unbind() { + super.unbind(); + icon.setCallback(null); + } } diff --git a/src/com/android/launcher2/ApplicationsAdapter.java b/src/com/android/launcher2/ApplicationsAdapter.java index 98f7c63..fc69703 100644 --- a/src/com/android/launcher2/ApplicationsAdapter.java +++ b/src/com/android/launcher2/ApplicationsAdapter.java @@ -28,7 +28,7 @@ import java.util.ArrayList; /** * GridView adapter to show the list of applications and shortcuts */ -public class ApplicationsAdapter extends ArrayAdapter<ApplicationInfo> { +public class ApplicationsAdapter extends ArrayAdapter<ApplicationInfo> { private final LayoutInflater mInflater; public ApplicationsAdapter(Context context, ArrayList<ApplicationInfo> apps) { @@ -45,7 +45,7 @@ public class ApplicationsAdapter extends ArrayAdapter<ApplicationInfo> { } if (!info.filtered) { - info.icon = Utilities.createIconThumbnail(info.icon, getContext()); + info.icon = Utilities.createIconThumbnail(info.icon, getContext(), false); info.filtered = true; } diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java index de375b6..eaaff1c 100644 --- a/src/com/android/launcher2/BubbleTextView.java +++ b/src/com/android/launcher2/BubbleTextView.java @@ -63,7 +63,6 @@ public class BubbleTextView extends TextView { setFocusable(true); mBackground = getBackground(); setBackgroundDrawable(null); - mBackground.setCallback(this); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(getContext().getResources().getColor(R.color.bubble_dark_background)); @@ -131,4 +130,16 @@ public class BubbleTextView extends TextView { super.draw(canvas); } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mBackground.setCallback(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mBackground.setCallback(null); + } } diff --git a/src/com/android/launcher2/DeferredHandler.java b/src/com/android/launcher2/DeferredHandler.java new file mode 100644 index 0000000..433bf55 --- /dev/null +++ b/src/com/android/launcher2/DeferredHandler.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher2; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.util.Log; + +import java.util.LinkedList; + +/** + * Queue of things to run on a looper thread. Items posted with {@link #post} will not + * be actually enqued on the handler until after the last one has run, to keep from + * starving the thread. + * + * This class is fifo. + */ +public class DeferredHandler { + private LinkedList<Runnable> mQueue = new LinkedList(); + private MessageQueue mMessageQueue = Looper.myQueue(); + private Impl mHandler = new Impl(); + + private class Impl extends Handler implements MessageQueue.IdleHandler { + public void handleMessage(Message msg) { + Runnable r; + synchronized (mQueue) { + r = mQueue.removeFirst(); + } + r.run(); + synchronized (mQueue) { + scheduleNextLocked(); + } + } + + public boolean queueIdle() { + handleMessage(null); + return false; + } + } + + private class IdleRunnable implements Runnable { + Runnable mRunnable; + + IdleRunnable(Runnable r) { + mRunnable = r; + } + + public void run() { + mRunnable.run(); + } + } + + public DeferredHandler() { + } + + /** Schedule runnable to run after everything that's on the queue right now. */ + public void post(Runnable runnable) { + synchronized (mQueue) { + mQueue.add(runnable); + if (mQueue.size() == 1) { + scheduleNextLocked(); + } + } + } + + /** Schedule runnable to run when the queue goes idle. */ + public void postIdle(final Runnable runnable) { + post(new IdleRunnable(runnable)); + } + + public void cancel() { + synchronized (mQueue) { + mQueue.clear(); + } + } + + void scheduleNextLocked() { + if (mQueue.size() > 0) { + Runnable peek = mQueue.getFirst(); + if (peek instanceof IdleRunnable) { + mMessageQueue.addIdleHandler(mHandler); + } else { + mHandler.sendEmptyMessage(1); + } + } + } +} + diff --git a/src/com/android/launcher2/DeleteZone.java b/src/com/android/launcher2/DeleteZone.java index 8a2545f..43fb1a6 100644 --- a/src/com/android/launcher2/DeleteZone.java +++ b/src/com/android/launcher2/DeleteZone.java @@ -93,24 +93,23 @@ public class DeleteZone extends ImageView implements DropTarget, DragController. if (item.container == -1) return; - final LauncherModel model = Launcher.getModel(); if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { if (item instanceof LauncherAppWidgetInfo) { - model.removeDesktopAppWidget((LauncherAppWidgetInfo) item); - } else { - model.removeDesktopItem(item); + mLauncher.removeAppWidget((LauncherAppWidgetInfo) item); } } else { if (source instanceof UserFolder) { final UserFolder userFolder = (UserFolder) source; final UserFolderInfo userFolderInfo = (UserFolderInfo) userFolder.getInfo(); - model.removeUserFolderItem(userFolderInfo, item); + // item must be an ApplicationInfo otherwise it couldn't have been in the folder + // in the first place. + userFolderInfo.remove((ApplicationInfo)item); } } if (item instanceof UserFolderInfo) { final UserFolderInfo userFolderInfo = (UserFolderInfo)item; LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher, userFolderInfo); - model.removeUserFolder(userFolderInfo); + mLauncher.removeFolder(userFolderInfo); } else if (item instanceof LauncherAppWidgetInfo) { final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java index 51cf5e1..38bc468 100644 --- a/src/com/android/launcher2/DragController.java +++ b/src/com/android/launcher2/DragController.java @@ -273,8 +273,10 @@ public class DragController { * Call this from a drag source view. */ public boolean onInterceptTouchEvent(MotionEvent ev) { - Log.d(Launcher.LOG_TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" - + mDragging); + if (false) { + Log.d(Launcher.LOG_TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" + + mDragging); + } final int action = ev.getAction(); final float screenX = ev.getRawX(); diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java index 85fc3a7..c135fa3 100644 --- a/src/com/android/launcher2/FolderIcon.java +++ b/src/com/android/launcher2/FolderIcon.java @@ -48,7 +48,7 @@ public class FolderIcon extends BubbleTextView implements DropTarget { final Resources resources = launcher.getResources(); Drawable d = resources.getDrawable(R.drawable.ic_launcher_folder); - d = Utilities.createIconThumbnail(d, launcher); + d = Utilities.createIconThumbnail(d, launcher, false); icon.mCloseIcon = d; icon.mOpenIcon = resources.getDrawable(R.drawable.ic_launcher_folder_open); icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null); diff --git a/src/com/android/launcher2/ItemInfo.java b/src/com/android/launcher2/ItemInfo.java index b371163..f04880d 100644 --- a/src/com/android/launcher2/ItemInfo.java +++ b/src/com/android/launcher2/ItemInfo.java @@ -130,4 +130,6 @@ class ItemInfo { } } + void unbind() { + } } diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index e388ac3..40f4b39 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -24,25 +24,20 @@ import android.app.SearchManager; import android.app.StatusBarManager; import android.app.WallpaperManager; import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; import android.content.Intent.ShortcutIconResource; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; @@ -74,6 +69,7 @@ import android.appwidget.AppWidgetProviderInfo; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.io.DataOutputStream; import java.io.FileNotFoundException; @@ -83,13 +79,15 @@ import java.io.DataInputStream; /** * Default launcher application. */ -public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener { +public final class Launcher extends Activity + implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks { static final String LOG_TAG = "Launcher"; + static final String TAG = LOG_TAG; static final boolean LOGD = false; - private static final boolean PROFILE_STARTUP = false; - private static final boolean PROFILE_ROTATE = false; - private static final boolean DEBUG_USER_INTERFACE = false; + static final boolean PROFILE_STARTUP = false; + static final boolean PROFILE_ROTATE = false; + static final boolean DEBUG_USER_INTERFACE = false; private static final int WALLPAPER_SCREENS_SPAN = 2; @@ -150,14 +148,11 @@ public final class Launcher extends Activity implements View.OnClickListener, On // Type: long private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; - private static final LauncherModel sModel = new LauncherModel(); + static final int APPWIDGET_HOST_ID = 1024; private static final Object sLock = new Object(); private static int sScreen = DEFAULT_SCREN; - private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver(); - private final ContentObserver mObserver = new FavoritesChangeObserver(); - private LayoutInflater mInflater; private DragController mDragController; @@ -166,8 +161,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On private AppWidgetManager mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; - static final int APPWIDGET_HOST_ID = 1024; - private CellLayout.CellInfo mAddItemCellInfo; private CellLayout.CellInfo mMenuAddInfo; private final int[] mCellCoordinates = new int[2]; @@ -178,15 +171,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On private AllAppsView mAllAppsGrid; private boolean mAllAppsVisible; - private boolean mDesktopLocked = true; private Bundle mSavedState; private SpannableStringBuilder mDefaultKeySsb = null; - private boolean mDestroyed; - private boolean mIsNewIntent; + private boolean mWorkspaceLoading = true; + private boolean mRestoring; private boolean mWaitingForResult; private boolean mLocaleChanged; @@ -196,12 +188,17 @@ public final class Launcher extends Activity implements View.OnClickListener, On private Bundle mSavedInstanceState; - private DesktopBinder mBinder; + private LauncherModel mModel; + + private ArrayList<ItemInfo> mDesktopItems = new ArrayList(); + private static HashMap<Long, FolderInfo> mFolders = new HashMap(); + private ArrayList<ApplicationInfo> mAllAppsList = new ArrayList(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mModel = ((LauncherApplication)getApplication()).setLauncher(this); mInflater = getLayoutInflater(); mAppWidgetManager = AppWidgetManager.getInstance(this); @@ -221,9 +218,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On lockAllApps(); - registerIntentReceivers(); - registerContentObservers(); - mSavedState = savedInstanceState; restoreState(mSavedState); @@ -232,7 +226,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On } if (!mRestoring) { - startLoaders(); + mModel.startLoader(this, true); } // For handling default keys @@ -263,6 +257,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On localeConfiguration.mnc = mnc; writeConfiguration(this, localeConfiguration); + AppInfoCache.flush(); } } @@ -330,13 +325,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } - private void startLoaders() { - boolean loadApplications = sModel.loadApplications(true, this, mLocaleChanged); - sModel.loadUserItems(!mLocaleChanged, this, mLocaleChanged, loadApplications); - - mRestoring = false; - } - private void setWallpaperDimension() { WallpaperManager wpm = (WallpaperManager)getSystemService(WALLPAPER_SERVICE); @@ -361,25 +349,25 @@ public final class Launcher extends Activity implements View.OnClickListener, On if (resultCode == RESULT_OK && mAddItemCellInfo != null) { switch (requestCode) { case REQUEST_PICK_APPLICATION: - completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked); + completeAddApplication(this, data, mAddItemCellInfo); break; case REQUEST_PICK_SHORTCUT: processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT); break; case REQUEST_CREATE_SHORTCUT: - completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked); + completeAddShortcut(data, mAddItemCellInfo); break; case REQUEST_PICK_LIVE_FOLDER: addLiveFolder(data); break; case REQUEST_CREATE_LIVE_FOLDER: - completeAddLiveFolder(data, mAddItemCellInfo, !mDesktopLocked); + completeAddLiveFolder(data, mAddItemCellInfo); break; case REQUEST_PICK_APPWIDGET: addAppWidget(data); break; case REQUEST_CREATE_APPWIDGET: - completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked); + completeAddAppWidget(data, mAddItemCellInfo); break; } } else if (requestCode == REQUEST_PICK_APPWIDGET && @@ -396,8 +384,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On protected void onResume() { super.onResume(); + Log.d(TAG, "onResume mRestoring=" + mRestoring); + if (mRestoring) { - startLoaders(); + mWorkspaceLoading = true; + mModel.startLoader(this, true); + mRestoring = false; } // If this was a new intent (i.e., the mIsNewIntent flag got set to true by @@ -430,10 +422,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On @Override public Object onRetainNonConfigurationInstance() { - // Flag any binder to stop early before switching - if (mBinder != null) { - mBinder.mTerminate = true; - } + // Flag the loader to stop early before switching + mModel.stopLoader(); if (PROFILE_ROTATE) { android.os.Debug.startMethodTracing("/sdcard/launcher-rotate"); @@ -512,7 +502,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false); if (renameFolder) { long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID); - mFolderInfo = sModel.getFolderById(this, id); + mFolderInfo = mModel.getFolderById(this, mFolders, id); mRestoring = true; } } @@ -583,7 +573,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false); if (!info.filtered) { - info.icon = Utilities.createIconThumbnail(info.icon, this); + info.icon = Utilities.createIconThumbnail(info.icon, this, false); info.filtered = true; } @@ -601,14 +591,13 @@ public final class Launcher extends Activity implements View.OnClickListener, On * @param data The intent describing the application. * @param cellInfo The position on screen where to create the shortcut. */ - void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo, - boolean insertAtFirst) { + void completeAddApplication(Context context, Intent data, CellLayout.CellInfo cellInfo) { cellInfo.screen = mWorkspace.getCurrentScreen(); if (!findSingleSlot(cellInfo)) return; final ApplicationInfo info = infoFromApplicationIntent(context, data); if (info != null) { - mWorkspace.addApplicationShortcut(info, cellInfo, insertAtFirst); + mWorkspace.addApplicationShortcut(info, cellInfo, isWorkspaceLocked()); } } @@ -646,22 +635,17 @@ public final class Launcher extends Activity implements View.OnClickListener, On * * @param data The intent describing the shortcut. * @param cellInfo The position on screen where to create the shortcut. - * @param insertAtFirst */ - private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo, - boolean insertAtFirst) { + private void completeAddShortcut(Intent data, CellLayout.CellInfo cellInfo) { cellInfo.screen = mWorkspace.getCurrentScreen(); if (!findSingleSlot(cellInfo)) return; final ApplicationInfo info = addShortcut(this, data, cellInfo, false); if (!mRestoring) { - sModel.addDesktopItem(info); - final View view = createShortcut(info); - mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst); - } else if (sModel.isDesktopLoaded()) { - sModel.addDesktopItem(info); + mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, + isWorkspaceLocked()); } } @@ -672,9 +656,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On * @param data The intent describing the appWidgetId. * @param cellInfo The position on screen where to create the widget. */ - private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo, - boolean insertAtFirst) { - + private void completeAddAppWidget(Intent data, CellLayout.CellInfo cellInfo) { Bundle extras = data.getExtras(); int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); @@ -700,7 +682,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On mWorkspace.getCurrentScreen(), xy[0], xy[1], false); if (!mRestoring) { - sModel.addDesktopAppWidget(launcherInfo); + mDesktopItems.add(launcherInfo); // Perform actual inflation because we're live launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); @@ -709,12 +691,15 @@ public final class Launcher extends Activity implements View.OnClickListener, On launcherInfo.hostView.setTag(launcherInfo); mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1], - launcherInfo.spanX, launcherInfo.spanY, insertAtFirst); - } else if (sModel.isDesktopLoaded()) { - sModel.addDesktopAppWidget(launcherInfo); + launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked()); } } + public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) { + mDesktopItems.remove(launcherInfo); + launcherInfo.hostView = null; + } + public LauncherAppWidgetHost getAppWidgetHost() { return mAppWidgetHost; } @@ -790,7 +775,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On try { dismissDialog(DIALOG_CREATE_SHORTCUT); // Unlock the workspace if the dialog was showing - mWorkspace.unlock(); } catch (Exception e) { // An exception is thrown if the dialog is not visible, which is fine } @@ -798,7 +782,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On try { dismissDialog(DIALOG_RENAME_FOLDER); // Unlock the workspace if the dialog was showing - mWorkspace.unlock(); } catch (Exception e) { // An exception is thrown if the dialog is not visible, which is fine } @@ -878,8 +861,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On @Override public void onDestroy() { - mDestroyed = true; - super.onDestroy(); try { @@ -891,11 +872,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On TextKeyListener.getInstance().release(); mAllAppsGrid.setAdapter(null); - sModel.unbind(); - sModel.abortLoaders(); + mModel.stopLoader(); - getContentResolver().unregisterContentObserver(mObserver); - unregisterReceiver(mApplicationsReceiver); + unbindDesktopItems(); + AppInfoCache.unbindDrawables(); } @Override @@ -975,7 +955,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On @Override public boolean onCreateOptionsMenu(Menu menu) { - if (mDesktopLocked) return false; + if (isWorkspaceLocked()) { + return false; + } super.onCreateOptionsMenu(menu); menu.add(MENU_GROUP_ADD, MENU_ADD, 0, R.string.menu_add) @@ -1043,20 +1025,12 @@ public final class Launcher extends Activity implements View.OnClickListener, On return true; } - private void addItems() { - showAddDialog(mMenuAddInfo); + public boolean isWorkspaceLocked() { + return mWorkspaceLoading || mWaitingForResult; } - private void removeShortcutsForPackage(String packageName) { - if (packageName != null && packageName.length() > 0) { - mWorkspace.removeShortcutsForPackage(packageName); - } - } - - private void updateShortcutsForPackage(String packageName) { - if (packageName != null && packageName.length() > 0) { - mWorkspace.updateShortcutsForPackage(packageName); - } + private void addItems() { + showAddDialog(mMenuAddInfo); } void addAppWidget(Intent data) { @@ -1096,7 +1070,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On if (!findSlot(cellInfo, xy, spanX, spanY)) return; - sModel.addDesktopItem(info); LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, mWorkspace.getCurrentScreen(), xy[0], xy[1], false); @@ -1131,13 +1104,13 @@ public final class Launcher extends Activity implements View.OnClickListener, On String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); if (folderName != null && folderName.equals(shortcutName)) { - addFolder(!mDesktopLocked); + addFolder(); } else { startActivityForResult(intent, REQUEST_CREATE_LIVE_FOLDER); } } - void addFolder(boolean insertAtFirst) { + void addFolder() { UserFolderInfo folderInfo = new UserFolderInfo(); folderInfo.title = getText(R.string.folder_name); @@ -1146,33 +1119,33 @@ public final class Launcher extends Activity implements View.OnClickListener, On if (!findSingleSlot(cellInfo)) return; // Update the model - LauncherModel.addItemToDatabase(this, folderInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, + LauncherModel.addItemToDatabase(this, folderInfo, + LauncherSettings.Favorites.CONTAINER_DESKTOP, mWorkspace.getCurrentScreen(), cellInfo.cellX, cellInfo.cellY, false); - sModel.addDesktopItem(folderInfo); - sModel.addFolder(folderInfo); + mFolders.put(folderInfo.id, folderInfo); // Create the view FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), folderInfo); mWorkspace.addInCurrentScreen(newFolder, - cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst); + cellInfo.cellX, cellInfo.cellY, 1, 1, isWorkspaceLocked()); + } + + void removeFolder(FolderInfo folder) { + mFolders.remove(folder.id); } - private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo, - boolean insertAtFirst) { + private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo) { cellInfo.screen = mWorkspace.getCurrentScreen(); if (!findSingleSlot(cellInfo)) return; final LiveFolderInfo info = addLiveFolder(this, data, cellInfo, false); if (!mRestoring) { - sModel.addDesktopItem(info); - final View view = LiveFolderIcon.fromXml(R.layout.live_folder_icon, this, (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info); - mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst); - } else if (sModel.isDesktopLoaded()) { - sModel.addDesktopItem(info); + mWorkspace.addInCurrentScreen(view, cellInfo.cellX, cellInfo.cellY, 1, 1, + isWorkspaceLocked()); } } @@ -1216,7 +1189,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); - sModel.addFolder(info); + mFolders.put(info.id, info); return info; } @@ -1256,28 +1229,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On startActivity(Intent.createChooser(pickWallpaper, getString(R.string.chooser_wallpaper))); } - /** - * Registers various intent receivers. The current implementation registers - * only a wallpaper intent receiver to let other applications change the - * wallpaper. - */ - private void registerIntentReceivers() { - IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addDataScheme("package"); - registerReceiver(mApplicationsReceiver, filter); - } - - /** - * Registers various content observers. The current implementation registers - * only a favorites observer to keep track of the favorites applications. - */ - private void registerContentObservers() { - ContentResolver resolver = getContentResolver(); - resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver); - } - @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); @@ -1331,202 +1282,19 @@ public final class Launcher extends Activity implements View.OnClickListener, On ViewGroup parent = (ViewGroup) folder.getParent(); if (parent != null) { parent.removeView(folder); - mDragController.removeDropTarget((DropTarget)folder); + // TODO: this line crashes. + //mDragController.removeDropTarget((DropTarget)folder); } folder.onClose(); } /** - * When the notification that favorites have changed is received, requests - * a favorites list refresh. + * Go through the and disconnect any of the callbacks in the drawables and the views or we + * leak the previous Home screen on orientation change. */ - private void onFavoritesChanged() { - mDesktopLocked = true; - lockAllApps(); - sModel.loadUserItems(false, this, false, false); - } - - void onDesktopItemsLoaded() { - if (mDestroyed) return; - bindDesktopItems(); - } - - /** - * Refreshes the shortcuts shown on the workspace. - */ - private void bindDesktopItems() { - final ArrayList<ItemInfo> shortcuts = sModel.getDesktopItems(); - final ArrayList<LauncherAppWidgetInfo> appWidgets = sModel.getDesktopAppWidgets(); - final ApplicationsAdapter drawerAdapter = sModel.getApplicationsAdapter(); - if (shortcuts == null || appWidgets == null || drawerAdapter == null) { - return; - } - - final Workspace workspace = mWorkspace; - int count = workspace.getChildCount(); - for (int i = 0; i < count; i++) { - ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout(); - } - - if (DEBUG_USER_INTERFACE) { - android.widget.Button finishButton = new android.widget.Button(this); - finishButton.setText("Finish"); - workspace.addInScreen(finishButton, 1, 0, 0, 1, 1); - - finishButton.setOnClickListener(new android.widget.Button.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); - } - - // Flag any old binder to terminate early - if (mBinder != null) { - mBinder.mTerminate = true; - } - - mBinder = new DesktopBinder(this, shortcuts, appWidgets, drawerAdapter); - mBinder.startBindingItems(); - } - - private void bindItems(Launcher.DesktopBinder binder, - ArrayList<ItemInfo> shortcuts, int start, int count) { - - final Workspace workspace = mWorkspace; - final boolean desktopLocked = mDesktopLocked; - - final int end = Math.min(start + DesktopBinder.ITEMS_COUNT, count); - int i = start; - - for ( ; i < end; i++) { - final ItemInfo item = shortcuts.get(i); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - final View shortcut = createShortcut((ApplicationInfo) item); - workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1, - !desktopLocked); - break; - case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: - final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), - (UserFolderInfo) item); - workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1, - !desktopLocked); - break; - case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: - final FolderIcon newLiveFolder = LiveFolderIcon.fromXml( - R.layout.live_folder_icon, this, - (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), - (LiveFolderInfo) item); - workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, - !desktopLocked); - break; - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: - final int screen = workspace.getCurrentScreen(); - final View view = mInflater.inflate(R.layout.widget_search, - (ViewGroup) workspace.getChildAt(screen), false); - - Search search = (Search) view.findViewById(R.id.widget_search); - search.setLauncher(this); - - final Widget widget = (Widget) item; - view.setTag(widget); - - workspace.addWidget(view, widget, !desktopLocked); - break; - } - } - - workspace.requestLayout(); - - if (end >= count) { - finishBindDesktopItems(); - binder.startBindingDrawer(); - } else { - binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget(); - } - } - - private void finishBindDesktopItems() { - if (mSavedState != null) { - if (!mWorkspace.hasFocus()) { - mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); - } - - final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); - if (userFolders != null) { - for (long folderId : userFolders) { - final FolderInfo info = sModel.findFolderById(folderId); - if (info != null) { - openFolder(info); - } - } - final Folder openFolder = mWorkspace.getOpenFolder(); - if (openFolder != null) { - openFolder.requestFocus(); - } - } - - final boolean allApps = mSavedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false); - if (allApps) { - showAllAppsDialog(); - } - - mSavedState = null; - } - - if (mSavedInstanceState != null) { - super.onRestoreInstanceState(mSavedInstanceState); - mSavedInstanceState = null; - } - - /* TODO - if (mAllAppsVisible && !mDrawer.hasFocus()) { - mDrawer.requestFocus(); - } - */ - - mDesktopLocked = false; - unlockAllApps(); - } - - private void bindDrawer(Launcher.DesktopBinder binder, - ApplicationsAdapter drawerAdapter) { - mAllAppsGrid.setAdapter(drawerAdapter); - binder.startBindingAppWidgetsWhenIdle(); - } - - private void bindAppWidgets(Launcher.DesktopBinder binder, - LinkedList<LauncherAppWidgetInfo> appWidgets) { - - final Workspace workspace = mWorkspace; - final boolean desktopLocked = mDesktopLocked; - - if (!appWidgets.isEmpty()) { - final LauncherAppWidgetInfo item = appWidgets.removeFirst(); - - final int appWidgetId = item.appWidgetId; - final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); - item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); - - if (LOGD) d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo)); - - item.hostView.setAppWidget(appWidgetId, appWidgetInfo); - item.hostView.setTag(item); - - workspace.addInScreen(item.hostView, item.screen, item.cellX, - item.cellY, item.spanX, item.spanY, !desktopLocked); - - workspace.requestLayout(); - } - - if (appWidgets.isEmpty()) { - if (PROFILE_ROTATE) { - android.os.Debug.stopMethodTracing(); - } - } else { - binder.obtainMessage(DesktopBinder.MESSAGE_BIND_APPWIDGETS).sendToTarget(); + private void unbindDesktopItems() { + for (ItemInfo item: mDesktopItems) { + item.unbind(); } } @@ -1618,18 +1386,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On openFolder.onOpen(); } - /** - * Returns true if the workspace is being loaded. When the workspace is loading, - * no user interaction should be allowed to avoid any conflict. - * - * @return True if the workspace is locked, false otherwise. - */ - boolean isWorkspaceLocked() { - return mDesktopLocked; - } - public boolean onLongClick(View v) { - if (mDesktopLocked) { + if (isWorkspaceLocked()) { return false; } @@ -1661,10 +1419,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On return true; } - static LauncherModel getModel() { - return sModel; - } - View getDrawerHandle() { return mHandleView; } @@ -1779,7 +1533,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On final AlertDialog dialog = builder.create(); dialog.setOnShowListener(new DialogInterface.OnShowListener() { public void onShow(DialogInterface dialog) { - mWorkspace.lock(); } }); @@ -1790,13 +1543,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On final String name = mInput.getText().toString(); if (!TextUtils.isEmpty(name)) { // Make sure we have the right folder info - mFolderInfo = sModel.findFolderById(mFolderInfo.id); + mFolderInfo = mFolders.get(mFolderInfo.id); mFolderInfo.title = name; LauncherModel.updateItemInDatabase(Launcher.this, mFolderInfo); - if (mDesktopLocked) { + if (mWorkspaceLoading) { lockAllApps(); - sModel.loadUserItems(false, Launcher.this, false, false); + mModel.setWorkspaceDirty(); + mModel.startLoader(Launcher.this, false); } else { final FolderIcon folderIcon = (FolderIcon) mWorkspace.getViewForTag(mFolderInfo); @@ -1804,9 +1558,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On folderIcon.setText(name); getWorkspace().requestLayout(); } else { - mDesktopLocked = true; lockAllApps(); - sModel.loadUserItems(false, Launcher.this, false, false); + mModel.setWorkspaceDirty(); + mWorkspaceLoading = true; + mModel.startLoader(Launcher.this, false); } } } @@ -1814,7 +1569,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On } private void cleanup() { - mWorkspace.unlock(); dismissDialog(DIALOG_RENAME_FOLDER); mWaitingForResult = false; mFolderInfo = null; @@ -1893,11 +1647,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On } public void onDismiss(DialogInterface dialog) { - mWorkspace.unlock(); } private void cleanup() { - mWorkspace.unlock(); dismissDialog(DIALOG_CREATE_SHORTCUT); } @@ -1974,158 +1726,215 @@ public final class Launcher extends Activity implements View.OnClickListener, On } public void onShow(DialogInterface dialog) { - mWorkspace.lock(); } } /** - * Receives notifications when applications are added/removed. + * Implementation of the method from LauncherModel.Callbacks. */ - private class ApplicationsIntentReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final String packageName = intent.getData().getSchemeSpecificPart(); - final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - - if (LauncherModel.DEBUG_LOADERS) { - d(LauncherModel.LOG_TAG, "application intent received: " + action + - ", replacing=" + replacing); - d(LauncherModel.LOG_TAG, " --> " + intent.getData()); - } + public int getCurrentWorkspaceScreen() { + return mWorkspace.getCurrentScreen(); + } - if (!Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - if (!replacing) { - removeShortcutsForPackage(packageName); - if (LauncherModel.DEBUG_LOADERS) { - d(LauncherModel.LOG_TAG, " --> remove package"); - } - sModel.removePackage(Launcher.this, packageName); - } - // else, we are replacing the package, so a PACKAGE_ADDED will be sent - // later, we will update the package at this time - } else { - if (!replacing) { - if (LauncherModel.DEBUG_LOADERS) { - d(LauncherModel.LOG_TAG, " --> add package"); - } - sModel.addPackage(Launcher.this, packageName); - } else { - if (LauncherModel.DEBUG_LOADERS) { - d(LauncherModel.LOG_TAG, " --> update package " + packageName); - } - sModel.updatePackage(Launcher.this, packageName); - updateShortcutsForPackage(packageName); - } - } - removeDialog(DIALOG_CREATE_SHORTCUT); - } else { - if (LauncherModel.DEBUG_LOADERS) { - d(LauncherModel.LOG_TAG, " --> sync package " + packageName); + /** + * Refreshes the shortcuts shown on the workspace. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void startBinding() { + final Workspace workspace = mWorkspace; + int count = workspace.getChildCount(); + for (int i = 0; i < count; i++) { + ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout(); + } + + if (DEBUG_USER_INTERFACE) { + android.widget.Button finishButton = new android.widget.Button(this); + finishButton.setText("Finish"); + workspace.addInScreen(finishButton, 1, 0, 0, 1, 1); + + finishButton.setOnClickListener(new android.widget.Button.OnClickListener() { + public void onClick(View v) { + finish(); } - sModel.syncPackage(Launcher.this, packageName); - } + }); } } /** - * Receives notifications whenever the user favorites have changed. + * Bind the items start-end from the list. + * + * Implementation of the method from LauncherModel.Callbacks. */ - private class FavoritesChangeObserver extends ContentObserver { - public FavoritesChangeObserver() { - super(new Handler()); - } + public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) { + + final Workspace workspace = mWorkspace; + + for (int i=start; i<end; i++) { + final ItemInfo item = shortcuts.get(i); + mDesktopItems.add(item); + switch (item.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + final View shortcut = createShortcut((ApplicationInfo) item); + workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1, + false); + break; + case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: + final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, + (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), + (UserFolderInfo) item); + workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1, + false); + break; + case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: + final FolderIcon newLiveFolder = LiveFolderIcon.fromXml( + R.layout.live_folder_icon, this, + (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()), + (LiveFolderInfo) item); + workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1, + false); + break; + case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: + final int screen = workspace.getCurrentScreen(); + final View view = mInflater.inflate(R.layout.widget_search, + (ViewGroup) workspace.getChildAt(screen), false); - @Override - public void onChange(boolean selfChange) { - onFavoritesChanged(); + Search search = (Search) view.findViewById(R.id.widget_search); + search.setLauncher(this); + + final Widget widget = (Widget) item; + view.setTag(widget); + + workspace.addWidget(view, widget, false); + break; + } } + + workspace.requestLayout(); + } + + /** + * Implementation of the method from LauncherModel.Callbacks. + */ + void bindFolders(HashMap<Long, FolderInfo> folders) { + mFolders.putAll(folders); } - private static class DesktopBinder extends Handler implements MessageQueue.IdleHandler { - static final int MESSAGE_BIND_ITEMS = 0x1; - static final int MESSAGE_BIND_APPWIDGETS = 0x2; - static final int MESSAGE_BIND_DRAWER = 0x3; + /** + * Add the views for a widget to the workspace. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAppWidget(LauncherAppWidgetInfo item) { + final Workspace workspace = mWorkspace; - // Number of items to bind in every pass - static final int ITEMS_COUNT = 6; + final int appWidgetId = item.appWidgetId; + final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); - private final ArrayList<ItemInfo> mShortcuts; - private final LinkedList<LauncherAppWidgetInfo> mAppWidgets; - private final ApplicationsAdapter mDrawerAdapter; - private final WeakReference<Launcher> mLauncher; + if (true) { + Log.d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", + appWidgetId, appWidgetInfo)); + } - public boolean mTerminate = false; + item.hostView.setAppWidget(appWidgetId, appWidgetInfo); + item.hostView.setTag(item); - DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts, - ArrayList<LauncherAppWidgetInfo> appWidgets, - ApplicationsAdapter drawerAdapter) { + workspace.addInScreen(item.hostView, item.screen, item.cellX, + item.cellY, item.spanX, item.spanY, false); - mLauncher = new WeakReference<Launcher>(launcher); - mShortcuts = shortcuts; - mDrawerAdapter = drawerAdapter; + workspace.requestLayout(); - // Sort widgets so active workspace is bound first - final int currentScreen = launcher.mWorkspace.getCurrentScreen(); - final int size = appWidgets.size(); - mAppWidgets = new LinkedList<LauncherAppWidgetInfo>(); + mDesktopItems.add(item); + } - for (int i = 0; i < size; i++) { - LauncherAppWidgetInfo appWidgetInfo = appWidgets.get(i); - if (appWidgetInfo.screen == currentScreen) { - mAppWidgets.addFirst(appWidgetInfo); - } else { - mAppWidgets.addLast(appWidgetInfo); + /** + * Callback saying that there aren't any more items to bind. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void finishBindingItems() { + if (mSavedState != null) { + if (!mWorkspace.hasFocus()) { + mWorkspace.getChildAt(mWorkspace.getCurrentScreen()).requestFocus(); + } + + final long[] userFolders = mSavedState.getLongArray(RUNTIME_STATE_USER_FOLDERS); + if (userFolders != null) { + for (long folderId : userFolders) { + final FolderInfo info = mFolders.get(folderId); + if (info != null) { + openFolder(info); + } + } + final Folder openFolder = mWorkspace.getOpenFolder(); + if (openFolder != null) { + openFolder.requestFocus(); } } - } - public void startBindingItems() { - obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget(); - } + final boolean allApps = mSavedState.getBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, false); + if (allApps) { + showAllAppsDialog(); + } - public void startBindingDrawer() { - obtainMessage(MESSAGE_BIND_DRAWER).sendToTarget(); + mSavedState = null; } - public void startBindingAppWidgetsWhenIdle() { - // Ask for notification when message queue becomes idle - final MessageQueue messageQueue = Looper.myQueue(); - messageQueue.addIdleHandler(this); + if (mSavedInstanceState != null) { + super.onRestoreInstanceState(mSavedInstanceState); + mSavedInstanceState = null; } - public boolean queueIdle() { - // Queue is idle, so start binding items - startBindingAppWidgets(); - return false; + /* TODO + if (mAllAppsVisible && !mDrawer.hasFocus()) { + mDrawer.requestFocus(); } + */ - public void startBindingAppWidgets() { - obtainMessage(MESSAGE_BIND_APPWIDGETS).sendToTarget(); - } + Log.d(TAG, "finishBindingItems done"); + mWorkspaceLoading = false; + } - @Override - public void handleMessage(Message msg) { - Launcher launcher = mLauncher.get(); - if (launcher == null || mTerminate) { - return; - } + /** + * Add the icons for all apps. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindAllApplications(ArrayList<ApplicationInfo> apps) { + Log.d(LOG_TAG, "got info for " + apps.size() + " apps"); + mAllAppsList = apps; + mAllAppsGrid.setApps(mAllAppsList); + } - switch (msg.what) { - case MESSAGE_BIND_ITEMS: { - launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2); - break; - } - case MESSAGE_BIND_DRAWER: { - launcher.bindDrawer(this, mDrawerAdapter); - break; - } - case MESSAGE_BIND_APPWIDGETS: { - launcher.bindAppWidgets(this, mAppWidgets); - break; - } - } - } + /** + * A package was installed. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindPackageAdded(ArrayList<ApplicationInfo> apps) { + removeDialog(DIALOG_CREATE_SHORTCUT); + } + + /** + * A package was updated. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps) { + removeDialog(DIALOG_CREATE_SHORTCUT); + mWorkspace.updateShortcutsForPackage(packageName); + } + + /** + * A package was uninstalled. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps) { + removeDialog(DIALOG_CREATE_SHORTCUT); + mWorkspace.removeShortcutsForPackage(packageName); } + } diff --git a/src/com/android/launcher2/LauncherAppWidgetInfo.java b/src/com/android/launcher2/LauncherAppWidgetInfo.java index cd46434..25db72b 100644 --- a/src/com/android/launcher2/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher2/LauncherAppWidgetInfo.java @@ -50,4 +50,11 @@ class LauncherAppWidgetInfo extends ItemInfo { public String toString() { return Integer.toString(appWidgetId); } + + + @Override + void unbind() { + super.unbind(); + hostView = null; + } } diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java index 03fe562..ee6006c 100644 --- a/src/com/android/launcher2/LauncherApplication.java +++ b/src/com/android/launcher2/LauncherApplication.java @@ -17,13 +17,74 @@ package com.android.launcher2; import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.os.Handler; import dalvik.system.VMRuntime; public class LauncherApplication extends Application { + public static final LauncherModel sModel = new LauncherModel(); + @Override public void onCreate() { VMRuntime.getRuntime().setMinimumHeapSize(4 * 1024 * 1024); super.onCreate(); + + // Register intent receivers + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + registerReceiver(mApplicationsReceiver, filter); + + // Register for changes to the favorites + ContentResolver resolver = getContentResolver(); + resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, + mFavoritesObserver); + } + + /** + * There's no guarantee that this function is ever called. + */ + @Override + public void onTerminate() { + super.onTerminate(); + + unregisterReceiver(mApplicationsReceiver); + + ContentResolver resolver = getContentResolver(); + resolver.unregisterContentObserver(mFavoritesObserver); + } + + /** + * Receives notifications when applications are added/removed. + */ + private final BroadcastReceiver mApplicationsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + sModel.onReceiveIntent(LauncherApplication.this, intent); + } + }; + + /** + * Receives notifications whenever the user favorites have changed. + */ + private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + // TODO: lockAllApps(); + sModel.setWorkspaceDirty(); + sModel.startLoader(LauncherApplication.this, false); + } + }; + + LauncherModel setLauncher(Launcher launcher) { + sModel.initialize(launcher); + return sModel; } } diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java index 7676b84..36b2090 100644 --- a/src/com/android/launcher2/LauncherModel.java +++ b/src/com/android/launcher2/LauncherModel.java @@ -31,15 +31,18 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.net.Uri; import static android.util.Log.*; +import android.util.Log; import android.os.Process; +import android.os.SystemClock; +import java.lang.ref.WeakReference; +import java.net.URISyntaxException; +import java.text.Collator; import java.util.ArrayList; +import java.util.Comparator; +import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Comparator; -import java.lang.ref.WeakReference; -import java.text.Collator; -import java.net.URISyntaxException; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one @@ -48,1096 +51,887 @@ import java.net.URISyntaxException; */ public class LauncherModel { static final boolean DEBUG_LOADERS = true; - static final String LOG_TAG = "HomeLoaders"; - - private static final int UI_NOTIFICATION_RATE = 4; - private static final int DEFAULT_APPLICATIONS_NUMBER = 42; - private static final long APPLICATION_NOT_RESPONDING_TIMEOUT = 5000; - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - private static final Collator sCollator = Collator.getInstance(); - - private boolean mApplicationsLoaded; - private boolean mDesktopItemsLoaded; - - private ArrayList<ItemInfo> mDesktopItems; - private ArrayList<LauncherAppWidgetInfo> mDesktopAppWidgets; - private HashMap<Long, FolderInfo> mFolders; - - private ArrayList<ApplicationInfo> mApplications; - private ApplicationsAdapter mApplicationsAdapter; - private ApplicationsLoader mApplicationsLoader; - private DesktopItemsLoader mDesktopItemsLoader; - private Thread mApplicationsLoaderThread; - private Thread mDesktopLoaderThread; - - private final HashMap<ComponentName, ApplicationInfo> mAppInfoCache = - new HashMap<ComponentName, ApplicationInfo>(INITIAL_ICON_CACHE_CAPACITY); - - synchronized void abortLoaders() { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - mApplicationsLoader.stop(); - mApplicationsLoaded = false; - } - - if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) { - mDesktopItemsLoader.stop(); - mDesktopItemsLoaded = false; - } + static final String TAG = "Launcher.Model"; + + private final Object mLock = new Object(); + private DeferredHandler mHandler = new DeferredHandler(); + private Loader mLoader = new Loader(); + + private WeakReference<Callbacks> mCallbacks; + + private AllAppsList mAllAppsList = new AllAppsList(); + + public interface Callbacks { + public int getCurrentWorkspaceScreen(); + public void startBinding(); + public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); + public void finishBindingItems(); + public void bindAppWidget(LauncherAppWidgetInfo info); + public void bindAllApplications(ArrayList<ApplicationInfo> apps); + public void bindPackageAdded(ArrayList<ApplicationInfo> apps); + public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps); + public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps); } - /** - * Drop our cache of components to their lables & icons. We do - * this from Launcher when applications are added/removed. It's a - * bit overkill, but it's a rare operation anyway. - */ - synchronized void dropApplicationCache() { - mAppInfoCache.clear(); - } /** - * Loads the list of installed applications in mApplications. - * - * @return true if the applications loader must be started - * (see startApplicationsLoader()), false otherwise. + * Adds an item to the DB if it was not created previously, or move it to a new + * <container, screen, cellX, cellY> */ - synchronized boolean loadApplications(boolean isLaunching, Launcher launcher, - boolean localeChanged) { - - if (DEBUG_LOADERS) d(LOG_TAG, "load applications"); - - if (isLaunching && mApplicationsLoaded && !localeChanged) { - mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications); - if (DEBUG_LOADERS) d(LOG_TAG, " --> applications loaded, return"); - return false; - } - - stopAndWaitForApplicationsLoader(); - - if (localeChanged) { - dropApplicationCache(); - } - - if (mApplicationsAdapter == null || isLaunching || localeChanged) { - mApplications = new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER); - mApplicationsAdapter = new ApplicationsAdapter(launcher, mApplications); - } - - mApplicationsLoaded = false; - - if (!isLaunching) { - startApplicationsLoader(launcher, false); - return false; + static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, + int screen, int cellX, int cellY) { + if (item.container == ItemInfo.NO_ID) { + // From all apps + addItemToDatabase(context, item, container, screen, cellX, cellY, false); + } else { + // From somewhere else + moveItemInDatabase(context, item, container, screen, cellX, cellY); } - - return true; } - private synchronized void stopAndWaitForApplicationsLoader() { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - if (DEBUG_LOADERS) d(LOG_TAG, " --> wait for applications loader"); - - mApplicationsLoader.stop(); - // Wait for the currently running thread to finish, this can take a little - // time but it should be well below the timeout limit - try { - mApplicationsLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT); - } catch (InterruptedException e) { - // EMpty - } - } - } + /** + * Move an item in the DB to a new <container, screen, cellX, cellY> + */ + static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, + int cellX, int cellY) { + item.container = container; + item.screen = screen; + item.cellX = cellX; + item.cellY = cellY; - private synchronized void startApplicationsLoader(Launcher launcher, boolean isLaunching) { - if (DEBUG_LOADERS) d(LOG_TAG, " --> starting applications loader"); + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); - stopAndWaitForApplicationsLoader(); + values.put(LauncherSettings.Favorites.CONTAINER, item.container); + values.put(LauncherSettings.Favorites.CELLX, item.cellX); + values.put(LauncherSettings.Favorites.CELLY, item.cellY); + values.put(LauncherSettings.Favorites.SCREEN, item.screen); - mApplicationsLoader = new ApplicationsLoader(launcher, isLaunching); - mApplicationsLoaderThread = new Thread(mApplicationsLoader, "Applications Loader"); - mApplicationsLoaderThread.start(); + cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); } - synchronized void addPackage(Launcher launcher, String packageName) { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - startApplicationsLoader(launcher, false); - return; - } - - if (packageName != null && packageName.length() > 0) { - final PackageManager packageManager = launcher.getPackageManager(); - final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName); - - if (matches.size() > 0) { - final ApplicationsAdapter adapter = mApplicationsAdapter; - final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache; - - for (ResolveInfo info : matches) { - adapter.setNotifyOnChange(false); - adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info, launcher)); - } - - adapter.sort(new ApplicationInfoComparator()); - adapter.notifyDataSetChanged(); - } + /** + * Returns true if the shortcuts already exists in the database. + * we identify a shortcut by its title and intent. + */ + static boolean shortcutExists(Context context, String title, Intent intent) { + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, + new String[] { "title", "intent" }, "title=? and intent=?", + new String[] { title, intent.toUri(0) }, null); + boolean result = false; + try { + result = c.moveToFirst(); + } finally { + c.close(); } + return result; } - synchronized void removePackage(Launcher launcher, String packageName) { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - dropApplicationCache(); // TODO: this could be optimized - startApplicationsLoader(launcher, false); - return; - } - - if (packageName != null && packageName.length() > 0) { - final ApplicationsAdapter adapter = mApplicationsAdapter; + /** + * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. + */ + FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { + final ContentResolver cr = context.getContentResolver(); + Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, + "_id=? and (itemType=? or itemType=?)", + new String[] { String.valueOf(id), + String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER), + String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null); - final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(); - final int count = adapter.getCount(); + try { + if (c.moveToFirst()) { + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - for (int i = 0; i < count; i++) { - final ApplicationInfo applicationInfo = adapter.getItem(i); - final Intent intent = applicationInfo.intent; - final ComponentName component = intent.getComponent(); - if (packageName.equals(component.getPackageName())) { - toRemove.add(applicationInfo); + FolderInfo folderInfo = null; + switch (c.getInt(itemTypeIndex)) { + case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: + folderInfo = findOrMakeUserFolder(folderList, id); + break; + case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: + folderInfo = findOrMakeLiveFolder(folderList, id); + break; } - } - final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache; - for (ApplicationInfo info : toRemove) { - adapter.setNotifyOnChange(false); - adapter.remove(info); - cache.remove(info.intent.getComponent()); - } + folderInfo.title = c.getString(titleIndex); + folderInfo.id = id; + folderInfo.container = c.getInt(containerIndex); + folderInfo.screen = c.getInt(screenIndex); + folderInfo.cellX = c.getInt(cellXIndex); + folderInfo.cellY = c.getInt(cellYIndex); - if (toRemove.size() > 0) { - adapter.sort(new ApplicationInfoComparator()); - adapter.notifyDataSetChanged(); + return folderInfo; } - } - } - - synchronized void updatePackage(Launcher launcher, String packageName) { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - startApplicationsLoader(launcher, false); - return; + } finally { + c.close(); } - if (packageName != null && packageName.length() > 0) { - final PackageManager packageManager = launcher.getPackageManager(); - final ApplicationsAdapter adapter = mApplicationsAdapter; + return null; + } - final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName); - final int count = matches.size(); + /** + * Add an item to the database in a specified container. Sets the container, screen, cellX and + * cellY fields of the item. Also assigns an ID to the item. + */ + static void addItemToDatabase(Context context, ItemInfo item, long container, + int screen, int cellX, int cellY, boolean notify) { + item.container = container; + item.screen = screen; + item.cellX = cellX; + item.cellY = cellY; - boolean changed = false; + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); - for (int i = 0; i < count; i++) { - final ResolveInfo info = matches.get(i); - final ApplicationInfo applicationInfo = findIntent(adapter, - info.activityInfo.applicationInfo.packageName, info.activityInfo.name); - if (applicationInfo != null) { - updateAndCacheApplicationInfo(packageManager, info, applicationInfo, launcher); - changed = true; - } - } + item.onAddToDatabase(values); - if (syncLocked(launcher, packageName)) changed = true; + Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : + LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); - if (changed) { - adapter.sort(new ApplicationInfoComparator()); - adapter.notifyDataSetChanged(); - } + if (result != null) { + item.id = Integer.parseInt(result.getPathSegments().get(1)); } } - private void updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info, - ApplicationInfo applicationInfo, Context context) { + /** + * Update an item to the database in a specified container. + */ + static void updateItemInDatabase(Context context, ItemInfo item) { + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); - updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo, context); + item.onAddToDatabase(values); - ComponentName componentName = new ComponentName( - info.activityInfo.applicationInfo.packageName, info.activityInfo.name); - mAppInfoCache.put(componentName, applicationInfo); + cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); } - synchronized void syncPackage(Launcher launcher, String packageName) { - if (mApplicationsLoader != null && mApplicationsLoader.isRunning()) { - startApplicationsLoader(launcher, false); - return; - } + /** + * Removes the specified item from the database + * @param context + * @param item + */ + static void deleteItemFromDatabase(Context context, ItemInfo item) { + final ContentResolver cr = context.getContentResolver(); - if (packageName != null && packageName.length() > 0) { - if (syncLocked(launcher, packageName)) { - final ApplicationsAdapter adapter = mApplicationsAdapter; - adapter.sort(new ApplicationInfoComparator()); - adapter.notifyDataSetChanged(); - } - } + cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null); } - private boolean syncLocked(Launcher launcher, String packageName) { - final PackageManager packageManager = launcher.getPackageManager(); - final List<ResolveInfo> matches = findActivitiesForPackage(packageManager, packageName); - - if (matches.size() > 0) { - final ApplicationsAdapter adapter = mApplicationsAdapter; - - // Find disabled activities and remove them from the adapter - boolean removed = removeDisabledActivities(packageName, matches, adapter); - // Find enable activities and add them to the adapter - // Also updates existing activities with new labels/icons - boolean added = addEnabledAndUpdateActivities(matches, adapter, launcher); - - return added || removed; - } + /** + * Remove the contents of the specified folder from the database + */ + static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) { + final ContentResolver cr = context.getContentResolver(); - return false; + cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); + cr.delete(LauncherSettings.Favorites.CONTENT_URI, + LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); } - private static List<ResolveInfo> findActivitiesForPackage(PackageManager packageManager, - String packageName) { - - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); - final List<ResolveInfo> matches = new ArrayList<ResolveInfo>(); - - if (apps != null) { - // Find all activities that match the packageName - int count = apps.size(); - for (int i = 0; i < count; i++) { - final ResolveInfo info = apps.get(i); - final ActivityInfo activityInfo = info.activityInfo; - if (packageName.equals(activityInfo.packageName)) { - matches.add(info); - } - } + /** + * Set this as the current Launcher activity object for the loader. + */ + public void initialize(Callbacks callbacks) { + synchronized (mLock) { + mCallbacks = new WeakReference<Callbacks>(callbacks); } + } - return matches; + public void startLoader(Context context, boolean isLaunching) { + mLoader.startLoader(context, isLaunching); } - private boolean addEnabledAndUpdateActivities(List<ResolveInfo> matches, - ApplicationsAdapter adapter, Launcher launcher) { + public void stopLoader() { + mLoader.stopLoader(); + } - final List<ApplicationInfo> toAdd = new ArrayList<ApplicationInfo>(); - final int count = matches.size(); + public void setWorkspaceDirty() { + mLoader.setWorkspaceDirty(); + } - boolean changed = false; + /** + * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and + * ACTION_PACKAGE_CHANGED. + */ + public void onReceiveIntent(Context context, Intent intent) { + final String packageName = intent.getData().getSchemeSpecificPart(); + + ArrayList<ApplicationInfo> added = null; + ArrayList<ApplicationInfo> removed = null; + ArrayList<ApplicationInfo> modified = null; + boolean update = false; + boolean remove = false; + + synchronized (mLock) { + final String action = intent.getAction(); + final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + + if (packageName == null || packageName.length() == 0) { + // they sent us a bad intent + return; + } - for (int i = 0; i < count; i++) { - final ResolveInfo info = matches.get(i); - final ApplicationInfo applicationInfo = findIntent(adapter, - info.activityInfo.applicationInfo.packageName, info.activityInfo.name); - if (applicationInfo == null) { - toAdd.add(makeAndCacheApplicationInfo(launcher.getPackageManager(), - mAppInfoCache, info, launcher)); - changed = true; + if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + mAllAppsList.updatePackage(context, packageName); + update = true; + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + if (!replacing) { + mAllAppsList.removePackage(packageName); + remove = true; + } + // else, we are replacing the package, so a PACKAGE_ADDED will be sent + // later, we will update the package at this time } else { - updateAndCacheApplicationInfo( - launcher.getPackageManager(), info, applicationInfo, launcher); - changed = true; + if (!replacing) { + mAllAppsList.addPackage(context, packageName); + } else { + mAllAppsList.updatePackage(context, packageName); + update = true; + } } - } - - for (ApplicationInfo info : toAdd) { - adapter.setNotifyOnChange(false); - adapter.add(info); - } - - return changed; - } - - private boolean removeDisabledActivities(String packageName, List<ResolveInfo> matches, - ApplicationsAdapter adapter) { - - final List<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(); - final int count = adapter.getCount(); - boolean changed = false; - - for (int i = 0; i < count; i++) { - final ApplicationInfo applicationInfo = adapter.getItem(i); - final Intent intent = applicationInfo.intent; - final ComponentName component = intent.getComponent(); - if (packageName.equals(component.getPackageName())) { - if (!findIntent(matches, component)) { - toRemove.add(applicationInfo); - changed = true; + if (mAllAppsList.added.size() > 0) { + added = mAllAppsList.added; + added = new ArrayList(); + } + if (mAllAppsList.removed.size() > 0) { + removed = mAllAppsList.removed; + removed = new ArrayList(); + for (ApplicationInfo info: removed) { + AppInfoCache.remove(info.intent.getComponent()); } } - } - - final HashMap<ComponentName, ApplicationInfo> cache = mAppInfoCache; - for (ApplicationInfo info : toRemove) { - adapter.setNotifyOnChange(false); - adapter.remove(info); - cache.remove(info.intent.getComponent()); - } - - return changed; - } - - private static ApplicationInfo findIntent(ApplicationsAdapter adapter, String packageName, - String name) { - - final int count = adapter.getCount(); - for (int i = 0; i < count; i++) { - final ApplicationInfo applicationInfo = adapter.getItem(i); - final Intent intent = applicationInfo.intent; - final ComponentName component = intent.getComponent(); - if (packageName.equals(component.getPackageName()) && - name.equals(component.getClassName())) { - return applicationInfo; + if (mAllAppsList.modified.size() > 0) { + modified = mAllAppsList.modified; + modified = new ArrayList(); } - } - - return null; - } - private static boolean findIntent(List<ResolveInfo> apps, ComponentName component) { - final String className = component.getClassName(); - for (ResolveInfo info : apps) { - final ActivityInfo activityInfo = info.activityInfo; - if (activityInfo.name.equals(className)) { - return true; + final Callbacks callbacks = mCallbacks.get(); + if (callbacks == null) { + return; } - } - return false; - } - - Drawable getApplicationInfoIcon(PackageManager manager, ApplicationInfo info) { - final ResolveInfo resolveInfo = manager.resolveActivity(info.intent, 0); - if (resolveInfo == null) { - return null; - } - - ComponentName componentName = new ComponentName( - resolveInfo.activityInfo.applicationInfo.packageName, - resolveInfo.activityInfo.name); - ApplicationInfo application = mAppInfoCache.get(componentName); - - if (application == null) { - return resolveInfo.activityInfo.loadIcon(manager); - } - - return application.icon; - } - - private static ApplicationInfo makeAndCacheApplicationInfo(PackageManager manager, - HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info, - Context context) { - - ComponentName componentName = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - ApplicationInfo application = appInfoCache.get(componentName); - if (application == null) { - application = new ApplicationInfo(); - application.container = ItemInfo.NO_ID; - - updateApplicationInfoTitleAndIcon(manager, info, application, context); - - application.setActivity(componentName, - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - - appInfoCache.put(componentName, application); - } - - return application; - } - - private static void updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info, - ApplicationInfo application, Context context) { - - application.title = info.loadLabel(manager); - if (application.title == null) { - application.title = info.activityInfo.name; + if (added != null) { + final ArrayList<ApplicationInfo> addedFinal = added; + mHandler.post(new Runnable() { + public void run() { + callbacks.bindPackageAdded(addedFinal); + } + }); + } + if (update || modified != null) { + final ArrayList<ApplicationInfo> modifiedFinal = modified; + mHandler.post(new Runnable() { + public void run() { + callbacks.bindPackageUpdated(packageName, modifiedFinal); + } + }); + } + if (remove || removed != null) { + final ArrayList<ApplicationInfo> removedFinal = removed; + mHandler.post(new Runnable() { + public void run() { + callbacks.bindPackageRemoved(packageName, removedFinal); + } + }); + } } - - application.icon = - Utilities.createIconThumbnail(info.activityInfo.loadIcon(manager), context); - application.filtered = false; } - private class ApplicationsLoader implements Runnable { - private final WeakReference<Launcher> mLauncher; + public class Loader { + private static final int ITEMS_CHUNK = 6; - private volatile boolean mStopped; - private volatile boolean mRunning; - private final boolean mIsLaunching; + private LoaderThread mLoaderThread; - ApplicationsLoader(Launcher launcher, boolean isLaunching) { - mIsLaunching = isLaunching; - mLauncher = new WeakReference<Launcher>(launcher); - } + private int mLastWorkspaceSeq = 0; + private int mWorkspaceSeq = 1; - void stop() { - mStopped = true; - } + private int mLastAllAppsSeq = 0; + private int mAllAppsSeq = 1; - boolean isRunning() { - return mRunning; + final ArrayList<ItemInfo> mItems = new ArrayList(); + final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList(); + final HashMap<Long, FolderInfo> folders = new HashMap(); + + /** + * Call this from the ui thread so the handler is initialized on the correct thread. + */ + public Loader() { } - public void run() { - mRunning = true; - - // Elevate priority when Home launches for the first time to avoid - // starving at boot time. Staring at a blank home is not cool. - android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT : - Process.THREAD_PRIORITY_BACKGROUND); - - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - final Launcher launcher = mLauncher.get(); - final PackageManager manager = launcher.getPackageManager(); - final List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent, 0); - - if (apps != null && !mStopped) { - final int count = apps.size(); - // Can be set to null on the UI thread by the unbind() method - // Do not access without checking for null first - final ApplicationsAdapter applicationList = mApplicationsAdapter; - - ChangeNotifier action = new ChangeNotifier(applicationList, true); - final HashMap<ComponentName, ApplicationInfo> appInfoCache = mAppInfoCache; - - for (int i = 0; i < count && !mStopped; i++) { - ResolveInfo info = apps.get(i); - ApplicationInfo application = - makeAndCacheApplicationInfo(manager, appInfoCache, info, launcher); - - if (action.add(application) && !mStopped) { - launcher.runOnUiThread(action); - action = new ChangeNotifier(applicationList, false); + public void startLoader(Context context, boolean isLaunching) { + synchronized (mLock) { + Log.d(TAG, "startLoader isLaunching=" + isLaunching); + // Don't bother to start the thread if we know it's not going to do anything + if (mCallbacks.get() != null) { + LoaderThread oldThread = mLoaderThread; + if (oldThread != null) { + if (oldThread.isLaunching()) { + // don't downgrade isLaunching if we're already running + isLaunching = true; + } + oldThread.stopLocked(); } + mLoaderThread = new LoaderThread(context, oldThread, isLaunching); + mLoaderThread.start(); } - - launcher.runOnUiThread(action); } - - if (!mStopped) { - mApplicationsLoaded = true; - } - mRunning = false; } - } - private static class ChangeNotifier implements Runnable { - private final ApplicationsAdapter mApplicationList; - private final ArrayList<ApplicationInfo> mBuffer; - - private boolean mFirst = true; - - ChangeNotifier(ApplicationsAdapter applicationList, boolean first) { - mApplicationList = applicationList; - mFirst = first; - mBuffer = new ArrayList<ApplicationInfo>(UI_NOTIFICATION_RATE); - } - - public void run() { - final ApplicationsAdapter applicationList = mApplicationList; - // Can be set to null on the UI thread by the unbind() method - if (applicationList == null) return; - - if (mFirst) { - applicationList.setNotifyOnChange(false); - applicationList.clear(); - mFirst = false; - } - - final ArrayList<ApplicationInfo> buffer = mBuffer; - final int count = buffer.size(); - - for (int i = 0; i < count; i++) { - applicationList.setNotifyOnChange(false); - applicationList.add(buffer.get(i)); + public void stopLoader() { + synchronized (mLock) { + if (mLoaderThread != null) { + mLoaderThread.stopLocked(); + } } - - buffer.clear(); - - applicationList.sort(new ApplicationInfoComparator()); - applicationList.notifyDataSetChanged(); - } - - boolean add(ApplicationInfo application) { - final ArrayList<ApplicationInfo> buffer = mBuffer; - buffer.add(application); - return buffer.size() >= UI_NOTIFICATION_RATE; } - } - static class ApplicationInfoComparator implements Comparator<ApplicationInfo> { - public final int compare(ApplicationInfo a, ApplicationInfo b) { - return sCollator.compare(a.title.toString(), b.title.toString()); - } - } - - boolean isDesktopLoaded() { - return mDesktopItems != null && mDesktopAppWidgets != null && mDesktopItemsLoaded; - } - - /** - * Loads all of the items on the desktop, in folders, or in the dock. - * These can be apps, shortcuts or widgets - */ - void loadUserItems(boolean isLaunching, Launcher launcher, boolean localeChanged, - boolean loadApplications) { - if (DEBUG_LOADERS) d(LOG_TAG, "loading user items"); - - if (isLaunching && isDesktopLoaded()) { - if (DEBUG_LOADERS) d(LOG_TAG, " --> items loaded, return"); - if (loadApplications) startApplicationsLoader(launcher, true); - // We have already loaded our data from the DB - launcher.onDesktopItemsLoaded(); - return; + public void setWorkspaceDirty() { + synchronized (mLock) { + mWorkspaceSeq++; + } } - if (mDesktopItemsLoader != null && mDesktopItemsLoader.isRunning()) { - if (DEBUG_LOADERS) d(LOG_TAG, " --> stopping workspace loader"); - mDesktopItemsLoader.stop(); - // Wait for the currently running thread to finish, this can take a little - // time but it should be well below the timeout limit - try { - mDesktopLoaderThread.join(APPLICATION_NOT_RESPONDING_TIMEOUT); - } catch (InterruptedException e) { - // Empty + public void setAllAppsDirty() { + synchronized (mLock) { + mAllAppsSeq++; } - - // If the thread we are interrupting was tasked to load the list of - // applications make sure we keep that information in the new loader - // spawned below - // note: we don't apply this to localeChanged because the thread can - // only be stopped *after* the localeChanged handling has occured - loadApplications = mDesktopItemsLoader.mLoadApplications; } - if (DEBUG_LOADERS) d(LOG_TAG, " --> starting workspace loader"); - mDesktopItemsLoaded = false; - mDesktopItemsLoader = new DesktopItemsLoader(launcher, localeChanged, loadApplications, - isLaunching); - mDesktopLoaderThread = new Thread(mDesktopItemsLoader, "Desktop Items Loader"); - mDesktopLoaderThread.start(); - } - - private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { - final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE, - LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE }, - null, null, null); - - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + /** + * Runnable for the thread that loads the contents of the launcher: + * - workspace icons + * - widgets + * - all apps icons + */ + private class LoaderThread extends Thread { + private Context mContext; + private Thread mWaitThread; + private boolean mIsLaunching; + private boolean mStopped; + private boolean mWorkspaceDoneBinding; + + LoaderThread(Context context, Thread waitThread, boolean isLaunching) { + mContext = context; + mWaitThread = waitThread; + mIsLaunching = isLaunching; + } - // boolean changed = false; + boolean isLaunching() { + return mIsLaunching; + } - try { - while (c.moveToNext()) { - try { - if (c.getInt(itemTypeIndex) != - LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - continue; + /** + * If another LoaderThread was supplied, we need to wait for that to finish before + * we start our processing. This keeps the ordering of the setting and clearing + * of the dirty flags correct by making sure we don't start processing stuff until + * they've had a chance to re-set them. We do this waiting the worker thread, not + * the ui thread to avoid ANRs. + */ + private void waitForOtherThread() { + if (mWaitThread != null) { + boolean done = false; + while (!done) { + try { + mWaitThread.join(); + } catch (InterruptedException ex) { + } } + mWaitThread = null; + } + } - final String intentUri = c.getString(intentIndex); - if (intentUri != null) { - final Intent shortcut = Intent.parseUri(intentUri, 0); - if (Intent.ACTION_MAIN.equals(shortcut.getAction())) { - final ComponentName name = shortcut.getComponent(); - if (name != null) { - final ActivityInfo activityInfo = manager.getActivityInfo(name, 0); - final String title = c.getString(titleIndex); - String label = getLabel(manager, activityInfo); + public void run() { + waitForOtherThread(); - if (title == null || !title.equals(label)) { - final ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.TITLE, label); + // Elevate priority when Home launches for the first time to avoid + // starving at boot time. Staring at a blank home is not cool. + synchronized (mLock) { + android.os.Process.setThreadPriority(mIsLaunching + ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); + } - resolver.update( - LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, - values, "_id=?", - new String[] { String.valueOf(c.getLong(idIndex)) }); + // Load the workspace only if it's dirty. + int workspaceSeq; + boolean workspaceDirty; + synchronized (mLock) { + workspaceSeq = mWorkspaceSeq; + workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq; + } + if (workspaceDirty) { + loadWorkspace(); + } + synchronized (mLock) { + // If we're not stopped, and nobody has incremented mWorkspaceSeq. + if (mStopped) { + return; + } + if (workspaceSeq == mWorkspaceSeq) { + mLastWorkspaceSeq = mWorkspaceSeq; + } + } - // changed = true; + // Bind the workspace + bindWorkspace(); + + // Wait until the either we're stopped or the other threads are done. + // This way we don't start loading all apps until the workspace has settled + // down. + synchronized (LoaderThread.this) { + mHandler.post(new Runnable() { + public void run() { + synchronized (LoaderThread.this) { + mWorkspaceDoneBinding = true; + Log.d(TAG, "done with workspace"); + LoaderThread.this.notify(); } } + }); + Log.d(TAG, "waiting to be done with workspace"); + while (!mStopped && !mWorkspaceDoneBinding) { + try { + this.wait(); + } catch (InterruptedException ex) { } } - } catch (URISyntaxException e) { - // Ignore - } catch (PackageManager.NameNotFoundException e) { - // Ignore + Log.d(TAG, "done waiting to be done with workspace"); } - } - } finally { - c.close(); - } - // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null); - } + // Load all apps if they're dirty + int allAppsSeq; + boolean allAppsDirty; + synchronized (mLock) { + allAppsSeq = mAllAppsSeq; + allAppsDirty = mAllAppsSeq != mLastAllAppsSeq; + } + if (allAppsDirty) { + loadAllApps(); + } + synchronized (mLock) { + // If we're not stopped, and nobody has incremented mAllAppsSeq. + if (mStopped) { + return; + } + if (allAppsSeq == mAllAppsSeq) { + mLastAllAppsSeq = mAllAppsSeq; + } + } - private static String getLabel(PackageManager manager, ActivityInfo activityInfo) { - String label = activityInfo.loadLabel(manager).toString(); - if (label == null) { - label = manager.getApplicationLabel(activityInfo.applicationInfo).toString(); - if (label == null) { - label = activityInfo.name; + // Bind all apps + bindAllApps(); + + // Clear out this reference, otherwise we end up holding it until all of the + // callback runnables are done. + mContext = null; + + synchronized (mLock) { + // Setting the reference is atomic, but we can't do it inside the other critical + // sections. + mLoaderThread = null; + return; + } } - } - return label; - } - private class DesktopItemsLoader implements Runnable { - private volatile boolean mStopped; - private volatile boolean mRunning; - - private final WeakReference<Launcher> mLauncher; - private final boolean mLocaleChanged; - private final boolean mLoadApplications; - private final boolean mIsLaunching; - - DesktopItemsLoader(Launcher launcher, boolean localeChanged, boolean loadApplications, - boolean isLaunching) { - mLoadApplications = loadApplications; - mIsLaunching = isLaunching; - mLauncher = new WeakReference<Launcher>(launcher); - mLocaleChanged = localeChanged; - } + public void stopLocked() { + synchronized (LoaderThread.this) { + mStopped = true; + this.notify(); + } + } - void stop() { - mStopped = true; - } + /** + * Gets the callbacks object. If we've been stopped, or if the launcher object + * has somehow been garbage collected, return null instead. + */ + Callbacks tryGetCallbacks() { + synchronized (mLock) { + if (mStopped) { + return null; + } - boolean isRunning() { - return mRunning; - } + final Callbacks callbacks = mCallbacks.get(); + if (callbacks == null) { + Log.w(TAG, "no mCallbacks"); + return null; + } - public void run() { - mRunning = true; + return callbacks; + } + } - android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + private void loadWorkspace() { + long t = SystemClock.uptimeMillis(); - final Launcher launcher = mLauncher.get(); - final ContentResolver contentResolver = launcher.getContentResolver(); - final PackageManager manager = launcher.getPackageManager(); + final Context context = mContext; + final ContentResolver contentResolver = context.getContentResolver(); + final PackageManager manager = context.getPackageManager(); - if (mLocaleChanged) { - updateShortcutLabels(contentResolver, manager); - } + /* TODO + if (mLocaleChanged) { + updateShortcutLabels(contentResolver, manager); + } + */ - mDesktopItems = new ArrayList<ItemInfo>(); - mDesktopAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); - mFolders = new HashMap<Long, FolderInfo>(); + final Cursor c = contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); - final ArrayList<ItemInfo> desktopItems = mDesktopItems; - final ArrayList<LauncherAppWidgetInfo> desktopAppWidgets = mDesktopAppWidgets; + try { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.ITEM_TYPE); + final int appWidgetIdIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.APPWIDGET_ID); + final int screenIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.CELLY); + final int spanXIndex = c.getColumnIndexOrThrow + (LauncherSettings.Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.SPANY); + final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + final int displayModeIndex = c.getColumnIndexOrThrow( + LauncherSettings.Favorites.DISPLAY_MODE); + + ApplicationInfo info; + String intentDescription; + Widget widgetInfo; + LauncherAppWidgetInfo appWidgetInfo; + int container; + long id; + Intent intent; + + while (!mStopped && c.moveToNext()) { + try { + int itemType = c.getInt(itemTypeIndex); - final Cursor c = contentResolver.query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); + switch (itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + intentDescription = c.getString(intentIndex); + try { + intent = Intent.parseUri(intentDescription, 0); + } catch (URISyntaxException e) { + continue; + } - try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); - final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); - final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); - final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); - final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); - final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID); - final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); - final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); - final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); - - ApplicationInfo info; - String intentDescription; - Widget widgetInfo; - LauncherAppWidgetInfo appWidgetInfo; - int container; - long id; - Intent intent; - - final HashMap<Long, FolderInfo> folders = mFolders; - - while (!mStopped && c.moveToNext()) { - try { - int itemType = c.getInt(itemTypeIndex); - - switch (itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - intentDescription = c.getString(intentIndex); - try { - intent = Intent.parseUri(intentDescription, 0); - } catch (java.net.URISyntaxException e) { - continue; - } + if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + info = getApplicationInfo(manager, intent, context); + } else { + info = getApplicationInfoShortcut(c, context, iconTypeIndex, + iconPackageIndex, iconResourceIndex, iconIndex); + } - if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = getApplicationInfo(manager, intent, launcher); - } else { - info = getApplicationInfoShortcut(c, launcher, iconTypeIndex, - iconPackageIndex, iconResourceIndex, iconIndex); - } + if (info == null) { + info = new ApplicationInfo(); + info.icon = manager.getDefaultActivityIcon(); + } - if (info == null) { - info = new ApplicationInfo(); - info.icon = manager.getDefaultActivityIcon(); - } + if (info != null) { + info.title = c.getString(titleIndex); + info.intent = intent; + + info.id = c.getLong(idIndex); + container = c.getInt(containerIndex); + info.container = container; + info.screen = c.getInt(screenIndex); + info.cellX = c.getInt(cellXIndex); + info.cellY = c.getInt(cellYIndex); + + switch (container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + mItems.add(info); + break; + default: + // Item is in a user folder + UserFolderInfo folderInfo = + findOrMakeUserFolder(folders, container); + folderInfo.add(info); + break; + } + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: + + id = c.getLong(idIndex); + UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id); - if (info != null) { - info.title = c.getString(titleIndex); - info.intent = intent; + folderInfo.title = c.getString(titleIndex); - info.id = c.getLong(idIndex); + folderInfo.id = id; container = c.getInt(containerIndex); - info.container = container; - info.screen = c.getInt(screenIndex); - info.cellX = c.getInt(cellXIndex); - info.cellY = c.getInt(cellYIndex); + folderInfo.container = container; + folderInfo.screen = c.getInt(screenIndex); + folderInfo.cellX = c.getInt(cellXIndex); + folderInfo.cellY = c.getInt(cellYIndex); switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - desktopItems.add(info); - break; - default: - // Item is in a user folder - UserFolderInfo folderInfo = - findOrMakeUserFolder(folders, container); - folderInfo.add(info); - break; + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + mItems.add(folderInfo); + break; + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: + + id = c.getLong(idIndex); + LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id); + + intentDescription = c.getString(intentIndex); + intent = null; + if (intentDescription != null) { + try { + intent = Intent.parseUri(intentDescription, 0); + } catch (URISyntaxException e) { + // Ignore, a live folder might not have a base intent + } } - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: - id = c.getLong(idIndex); - UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id); + liveFolderInfo.title = c.getString(titleIndex); + liveFolderInfo.id = id; + container = c.getInt(containerIndex); + liveFolderInfo.container = container; + liveFolderInfo.screen = c.getInt(screenIndex); + liveFolderInfo.cellX = c.getInt(cellXIndex); + liveFolderInfo.cellY = c.getInt(cellYIndex); + liveFolderInfo.uri = Uri.parse(c.getString(uriIndex)); + liveFolderInfo.baseIntent = intent; + liveFolderInfo.displayMode = c.getInt(displayModeIndex); - folderInfo.title = c.getString(titleIndex); + loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex, + iconResourceIndex, liveFolderInfo); - folderInfo.id = id; - container = c.getInt(containerIndex); - folderInfo.container = container; - folderInfo.screen = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); + switch (container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: + mItems.add(liveFolderInfo); + break; + } + break; + case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: + widgetInfo = Widget.makeSearch(); - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - desktopItems.add(folderInfo); - break; - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + Log.e(TAG, "Widget found where container " + + "!= CONTAINER_DESKTOP ignoring!"); + continue; + } - id = c.getLong(idIndex); - LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id); + widgetInfo.id = c.getLong(idIndex); + widgetInfo.screen = c.getInt(screenIndex); + widgetInfo.container = container; + widgetInfo.cellX = c.getInt(cellXIndex); + widgetInfo.cellY = c.getInt(cellYIndex); + + mItems.add(widgetInfo); + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + // Read all Launcher-specific widget details + int appWidgetId = c.getInt(appWidgetIdIndex); + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); + appWidgetInfo.id = c.getLong(idIndex); + appWidgetInfo.screen = c.getInt(screenIndex); + appWidgetInfo.cellX = c.getInt(cellXIndex); + appWidgetInfo.cellY = c.getInt(cellYIndex); + appWidgetInfo.spanX = c.getInt(spanXIndex); + appWidgetInfo.spanY = c.getInt(spanYIndex); - intentDescription = c.getString(intentIndex); - intent = null; - if (intentDescription != null) { - try { - intent = Intent.parseUri(intentDescription, 0); - } catch (java.net.URISyntaxException e) { - // Ignore, a live folder might not have a base intent + container = c.getInt(containerIndex); + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + Log.e(TAG, "Widget found where container " + + "!= CONTAINER_DESKTOP -- ignoring!"); + continue; } - } + appWidgetInfo.container = c.getInt(containerIndex); - liveFolderInfo.title = c.getString(titleIndex); - liveFolderInfo.id = id; - container = c.getInt(containerIndex); - liveFolderInfo.container = container; - liveFolderInfo.screen = c.getInt(screenIndex); - liveFolderInfo.cellX = c.getInt(cellXIndex); - liveFolderInfo.cellY = c.getInt(cellYIndex); - liveFolderInfo.uri = Uri.parse(c.getString(uriIndex)); - liveFolderInfo.baseIntent = intent; - liveFolderInfo.displayMode = c.getInt(displayModeIndex); - - loadLiveFolderIcon(launcher, c, iconTypeIndex, iconPackageIndex, - iconResourceIndex, liveFolderInfo); - - switch (container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: - desktopItems.add(liveFolderInfo); - break; - } - break; - case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: - widgetInfo = Widget.makeSearch(); - - container = c.getInt(containerIndex); - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { - e(Launcher.LOG_TAG, "Widget found where container " - + "!= CONTAINER_DESKTOP ignoring!"); - continue; + mAppWidgets.add(appWidgetInfo); + break; } - - widgetInfo.id = c.getLong(idIndex); - widgetInfo.screen = c.getInt(screenIndex); - widgetInfo.container = container; - widgetInfo.cellX = c.getInt(cellXIndex); - widgetInfo.cellY = c.getInt(cellYIndex); - - desktopItems.add(widgetInfo); - break; - case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: - // Read all Launcher-specific widget details - int appWidgetId = c.getInt(appWidgetIdIndex); - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); - appWidgetInfo.id = c.getLong(idIndex); - appWidgetInfo.screen = c.getInt(screenIndex); - appWidgetInfo.cellX = c.getInt(cellXIndex); - appWidgetInfo.cellY = c.getInt(cellYIndex); - appWidgetInfo.spanX = c.getInt(spanXIndex); - appWidgetInfo.spanY = c.getInt(spanYIndex); - - container = c.getInt(containerIndex); - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { - e(Launcher.LOG_TAG, "Widget found where container " - + "!= CONTAINER_DESKTOP -- ignoring!"); - continue; - } - appWidgetInfo.container = c.getInt(containerIndex); - - desktopAppWidgets.add(appWidgetInfo); - break; + } catch (Exception e) { + Log.w(TAG, "Desktop items loading interrupted:", e); } - } catch (Exception e) { - w(Launcher.LOG_TAG, "Desktop items loading interrupted:", e); } + } finally { + c.close(); } - } finally { - c.close(); + Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); } - if (!mStopped) { - launcher.runOnUiThread(new Runnable() { + /** + * Read everything out of our database. + */ + private void bindWorkspace() { + final long t = SystemClock.uptimeMillis(); + + // Don't use these two variables in any of the callback runnables. + // Otherwise we hold a reference to them. + Callbacks callbacks = mCallbacks.get(); + if (callbacks == null) { + // This launcher has exited and nobody bothered to tell us. Just bail. + Log.w(TAG, "LoaderThread running with no launcher"); + return; + } + + int N; + // Tell the workspace that we're about to start firing items at it + mHandler.post(new Runnable() { public void run() { - launcher.onDesktopItemsLoaded(); + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.startBinding(); + } + } + }); + // Add the items to the workspace. + N = mItems.size(); + for (int i=0; i<N; i+=ITEMS_CHUNK) { + final int start = i; + final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.bindItems(mItems, start, start+chunkSize); + } + } + }); + } + // Wait until the queue goes empty. + mHandler.postIdle(new Runnable() { + public void run() { + Log.d(TAG, "Going to start binding widgets soon."); + } + }); + // Bind the widgets, one at a time. + // WARNING: this is calling into the workspace from the background thread, + // but since getCurrentScreen() just returns the int, we should be okay. This + // is just a hint for the order, and if it's wrong, we'll be okay. + // TODO: instead, we should have that push the current screen into here. + final int currentScreen = callbacks.getCurrentWorkspaceScreen(); + N = mAppWidgets.size(); + // once for the current screen + for (int i=0; i<N; i++) { + final LauncherAppWidgetInfo widget = mAppWidgets.get(i); + if (widget.screen == currentScreen) { + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.bindAppWidget(widget); + } + } + }); + } + } + // once for the other screens + for (int i=0; i<N; i++) { + final LauncherAppWidgetInfo widget = mAppWidgets.get(i); + if (widget.screen != currentScreen) { + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.bindAppWidget(widget); + } + } + }); + } + } + // TODO: Bind the folders + // Tell the workspace that we're done. + mHandler.post(new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.finishBindingItems(); + } + } + }); + // If we're profiling, this is the last thing in the queue. + mHandler.post(new Runnable() { + public void run() { + Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); + if (Launcher.PROFILE_ROTATE) { + android.os.Debug.stopMethodTracing(); + } } }); - if (mLoadApplications) startApplicationsLoader(launcher, mIsLaunching); - } - - if (!mStopped) { - mDesktopItemsLoaded = true; } - mRunning = false; - } - } - private static void loadLiveFolderIcon(Launcher launcher, Cursor c, int iconTypeIndex, - int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { + private void loadAllApps() { + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - int iconType = c.getInt(iconTypeIndex); - switch (iconType) { - case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: - String packageName = c.getString(iconPackageIndex); - String resourceName = c.getString(iconResourceIndex); - PackageManager packageManager = launcher.getPackageManager(); - try { - Resources resources = packageManager.getResourcesForApplication(packageName); - final int id = resources.getIdentifier(resourceName, null, null); - liveFolderInfo.icon = resources.getDrawable(id); - } catch (Exception e) { - liveFolderInfo.icon = - launcher.getResources().getDrawable(R.drawable.ic_launcher_folder); + final Callbacks callbacks = tryGetCallbacks(); + if (callbacks == null) { + return; } - liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); - liveFolderInfo.iconResource.packageName = packageName; - liveFolderInfo.iconResource.resourceName = resourceName; - break; - default: - liveFolderInfo.icon = - launcher.getResources().getDrawable(R.drawable.ic_launcher_folder); - } - } - /** - * Finds the user folder defined by the specified id. - * - * @param id The id of the folder to look for. - * - * @return A UserFolderInfo if the folder exists or null otherwise. - */ - FolderInfo findFolderById(long id) { - return mFolders.get(id); - } + final Context context = mContext; + final PackageManager packageManager = context.getPackageManager(); - void addFolder(FolderInfo info) { - mFolders.put(info.id, info); - } + final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); - /** - * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a - * new one. - */ - private UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) { - // See if a placeholder was created for us already - FolderInfo folderInfo = folders.get(id); - if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) { - // No placeholder -- create a new instance - folderInfo = new UserFolderInfo(); - folders.put(id, folderInfo); - } - return (UserFolderInfo) folderInfo; - } + synchronized (mLock) { + mAllAppsList.clear(); + if (apps != null) { + long t = SystemClock.uptimeMillis(); - /** - * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a - * new one. - */ - private LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) { - // See if a placeholder was created for us already - FolderInfo folderInfo = folders.get(id); - if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) { - // No placeholder -- create a new instance - folderInfo = new LiveFolderInfo(); - folders.put(id, folderInfo); - } - return (LiveFolderInfo) folderInfo; - } - - /** - * Remove the callback for the cached drawables or we leak the previous - * Home screen on orientation change. - */ - void unbind() { - // Interrupt the applications loader before setting the adapter to null - stopAndWaitForApplicationsLoader(); - mApplicationsAdapter = null; - unbindAppDrawables(mApplications); - unbindDrawables(mDesktopItems); - unbindAppWidgetHostViews(mDesktopAppWidgets); - unbindCachedIconDrawables(); - } - - /** - * Remove the callback for the cached drawables or we leak the previous - * Home screen on orientation change. - */ - private void unbindDrawables(ArrayList<ItemInfo> desktopItems) { - if (desktopItems != null) { - final int count = desktopItems.size(); - for (int i = 0; i < count; i++) { - ItemInfo item = desktopItems.get(i); - switch (item.itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - ((ApplicationInfo)item).icon.setCallback(null); - break; + int N = apps.size(); + Utilities.BubbleText bubble = new Utilities.BubbleText(context); + for (int i=0; i<N && !mStopped; i++) { + // This builds the icon bitmaps. + mAllAppsList.add(AppInfoCache.cache(apps.get(i), context, bubble)); + } + Collections.sort(mAllAppsList.data, sComparator); + Collections.sort(mAllAppsList.added, sComparator); + Log.d(TAG, "cached app icons in " + (SystemClock.uptimeMillis()-t) + "ms"); + } } } - } - } - /** - * Remove the callback for the cached drawables or we leak the previous - * Home screen on orientation change. - */ - private void unbindAppDrawables(ArrayList<ApplicationInfo> applications) { - if (applications != null) { - final int count = applications.size(); - for (int i = 0; i < count; i++) { - applications.get(i).icon.setCallback(null); - } - } - } + private void bindAllApps() { + synchronized (mLock) { + final ArrayList<ApplicationInfo> results = mAllAppsList.added; + mAllAppsList.added = new ArrayList(); + mHandler.post(new Runnable() { + public void run() { + long t = SystemClock.uptimeMillis(); + + Callbacks callbacks = tryGetCallbacks(); + if (callbacks != null) { + callbacks.bindAllApplications(results); + } - /** - * Remove any {@link LauncherAppWidgetHostView} references in our widgets. - */ - private void unbindAppWidgetHostViews(ArrayList<LauncherAppWidgetInfo> appWidgets) { - if (appWidgets != null) { - final int count = appWidgets.size(); - for (int i = 0; i < count; i++) { - LauncherAppWidgetInfo launcherInfo = appWidgets.get(i); - launcherInfo.hostView = null; + Log.d(TAG, "bound app icons in " + + (SystemClock.uptimeMillis()-t) + "ms"); + } + }); + } } } } /** - * Remove the callback for the cached drawables or we leak the previous - * Home screen on orientation change. - */ - private void unbindCachedIconDrawables() { - for (ApplicationInfo appInfo : mAppInfoCache.values()) { - appInfo.icon.setCallback(null); - } - } - - /** - * @return The current list of applications - */ - ApplicationsAdapter getApplicationsAdapter() { - return mApplicationsAdapter; - } - - /** - * @return The current list of desktop items - */ - ArrayList<ItemInfo> getDesktopItems() { - return mDesktopItems; - } - - /** - * @return The current list of desktop items - */ - ArrayList<LauncherAppWidgetInfo> getDesktopAppWidgets() { - return mDesktopAppWidgets; - } - - /** - * Add an item to the desktop - * @param info - */ - void addDesktopItem(ItemInfo info) { - // TODO: write to DB; also check that folder has been added to folders list - mDesktopItems.add(info); - } - - /** - * Remove an item from the desktop - * @param info - */ - void removeDesktopItem(ItemInfo info) { - // TODO: write to DB; figure out if we should remove folder from folders list - mDesktopItems.remove(info); - } - - /** - * Add a widget to the desktop - */ - void addDesktopAppWidget(LauncherAppWidgetInfo info) { - mDesktopAppWidgets.add(info); - } - - /** - * Remove a widget from the desktop - */ - void removeDesktopAppWidget(LauncherAppWidgetInfo info) { - mDesktopAppWidgets.remove(info); - } - - /** - * Make an ApplicationInfo object for an application + * Make an ApplicationInfo object for an application. */ private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent, Context context) { @@ -1149,7 +943,7 @@ public class LauncherModel { final ApplicationInfo info = new ApplicationInfo(); final ActivityInfo activityInfo = resolveInfo.activityInfo; - info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context); + info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context, false); if (info.title == null || info.title.length() == 0) { info.title = activityInfo.loadLabel(manager); } @@ -1163,7 +957,7 @@ public class LauncherModel { /** * Make an ApplicationInfo object for a sortcut */ - private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context, + private static ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) { final ApplicationInfo info = new ApplicationInfo(); @@ -1171,312 +965,175 @@ public class LauncherModel { int iconType = c.getInt(iconTypeIndex); switch (iconType) { - case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: - String packageName = c.getString(iconPackageIndex); - String resourceName = c.getString(iconResourceIndex); - PackageManager packageManager = context.getPackageManager(); - try { - Resources resources = packageManager.getResourcesForApplication(packageName); - final int id = resources.getIdentifier(resourceName, null, null); - info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context); - } catch (Exception e) { - info.icon = packageManager.getDefaultActivityIcon(); - } - info.iconResource = new Intent.ShortcutIconResource(); - info.iconResource.packageName = packageName; - info.iconResource.resourceName = resourceName; - info.customIcon = false; - break; - case LauncherSettings.Favorites.ICON_TYPE_BITMAP: - byte[] data = c.getBlob(iconIndex); - try { - Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); - info.icon = new FastBitmapDrawable( - Utilities.createBitmapThumbnail(bitmap, context)); - } catch (Exception e) { - packageManager = context.getPackageManager(); - info.icon = packageManager.getDefaultActivityIcon(); - } - info.filtered = true; - info.customIcon = true; - break; - default: - info.icon = context.getPackageManager().getDefaultActivityIcon(); - info.customIcon = false; - break; + case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: + String packageName = c.getString(iconPackageIndex); + String resourceName = c.getString(iconResourceIndex); + PackageManager packageManager = context.getPackageManager(); + try { + Resources resources = packageManager.getResourcesForApplication(packageName); + final int id = resources.getIdentifier(resourceName, null, null); + info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context, + false); + } catch (Exception e) { + info.icon = packageManager.getDefaultActivityIcon(); + } + info.iconResource = new Intent.ShortcutIconResource(); + info.iconResource.packageName = packageName; + info.iconResource.resourceName = resourceName; + info.customIcon = false; + break; + case LauncherSettings.Favorites.ICON_TYPE_BITMAP: + byte[] data = c.getBlob(iconIndex); + try { + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + info.icon = new FastBitmapDrawable( + Utilities.createBitmapThumbnail(bitmap, context)); + } catch (Exception e) { + packageManager = context.getPackageManager(); + info.icon = packageManager.getDefaultActivityIcon(); + } + info.filtered = true; + info.customIcon = true; + break; + default: + info.icon = context.getPackageManager().getDefaultActivityIcon(); + info.customIcon = false; + break; } return info; } - /** - * Remove an item from the in-memory represention of a user folder. Does not change the DB. - */ - void removeUserFolderItem(UserFolderInfo folder, ItemInfo info) { - //noinspection SuspiciousMethodCalls - folder.contents.remove(info); - } - - /** - * Removes a UserFolder from the in-memory list of folders. Does not change the DB. - * @param userFolderInfo - */ - void removeUserFolder(UserFolderInfo userFolderInfo) { - mFolders.remove(userFolderInfo.id); - } - - /** - * Adds an item to the DB if it was not created previously, or move it to a new - * <container, screen, cellX, cellY> - */ - static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, - int screen, int cellX, int cellY) { - if (item.container == ItemInfo.NO_ID) { - // From all apps - addItemToDatabase(context, item, container, screen, cellX, cellY, false); - } else { - // From somewhere else - moveItemInDatabase(context, item, container, screen, cellX, cellY); - } - } - - /** - * Move an item in the DB to a new <container, screen, cellX, cellY> - */ - static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen, - int cellX, int cellY) { - item.container = container; - item.screen = screen; - item.cellX = cellX; - item.cellY = cellY; - - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - - values.put(LauncherSettings.Favorites.CONTAINER, item.container); - values.put(LauncherSettings.Favorites.CELLX, item.cellX); - values.put(LauncherSettings.Favorites.CELLY, item.cellY); - values.put(LauncherSettings.Favorites.SCREEN, item.screen); - - cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); - } - - /** - * Returns true if the shortcuts already exists in the database. - * we identify a shortcut by its title and intent. - */ - static boolean shortcutExists(Context context, String title, Intent intent) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { "title", "intent" }, "title=? and intent=?", - new String[] { title, intent.toUri(0) }, null); - boolean result = false; - try { - result = c.moveToFirst(); - } finally { - c.close(); - } - return result; - } - - FolderInfo getFolderById(Context context, long id) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, - "_id=? and (itemType=? or itemType=?)", - new String[] { String.valueOf(id), - String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER), - String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null); - - try { - if (c.moveToFirst()) { - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); - final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - - FolderInfo folderInfo = null; - switch (c.getInt(itemTypeIndex)) { - case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: - folderInfo = findOrMakeUserFolder(mFolders, id); - break; - case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: - folderInfo = findOrMakeLiveFolder(mFolders, id); - break; - } - - folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; - folderInfo.container = c.getInt(containerIndex); - folderInfo.screen = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); + private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, + int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { - return folderInfo; + int iconType = c.getInt(iconTypeIndex); + switch (iconType) { + case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: + String packageName = c.getString(iconPackageIndex); + String resourceName = c.getString(iconResourceIndex); + PackageManager packageManager = context.getPackageManager(); + try { + Resources resources = packageManager.getResourcesForApplication(packageName); + final int id = resources.getIdentifier(resourceName, null, null); + liveFolderInfo.icon = resources.getDrawable(id); + } catch (Exception e) { + liveFolderInfo.icon = + context.getResources().getDrawable(R.drawable.ic_launcher_folder); } - } finally { - c.close(); + liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); + liveFolderInfo.iconResource.packageName = packageName; + liveFolderInfo.iconResource.resourceName = resourceName; + break; + default: + liveFolderInfo.icon = + context.getResources().getDrawable(R.drawable.ic_launcher_folder); } - - return null; } /** - * Add an item to the database in a specified container. Sets the container, screen, cellX and - * cellY fields of the item. Also assigns an ID to the item. + * Return an existing UserFolderInfo object if we have encountered this ID previously, + * or make a new one. */ - static void addItemToDatabase(Context context, ItemInfo item, long container, - int screen, int cellX, int cellY, boolean notify) { - item.container = container; - item.screen = screen; - item.cellX = cellX; - item.cellY = cellY; - - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - - item.onAddToDatabase(values); - - Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : - LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); - - if (result != null) { - item.id = Integer.parseInt(result.getPathSegments().get(1)); + private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) { + // See if a placeholder was created for us already + FolderInfo folderInfo = folders.get(id); + if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) { + // No placeholder -- create a new instance + folderInfo = new UserFolderInfo(); + folders.put(id, folderInfo); } + return (UserFolderInfo) folderInfo; } /** - * Add an item to the database in a specified container. Sets the container, screen, cellX and - * cellY fields of the item. Also assigns an ID to the item. + * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a + * new one. */ - static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) { - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - - item.onAddToDatabase(values); - - Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI : - LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values); - - if (result != null) { - item.id = Integer.parseInt(result.getPathSegments().get(1)); + private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) { + // See if a placeholder was created for us already + FolderInfo folderInfo = folders.get(id); + if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) { + // No placeholder -- create a new instance + folderInfo = new LiveFolderInfo(); + folders.put(id, folderInfo); } - - return result != null; - } - - /** - * Update an item to the database in a specified container. - */ - static void updateItemInDatabase(Context context, ItemInfo item) { - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - - item.onAddToDatabase(values); - - cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null); - } - - /** - * Removes the specified item from the database - * @param context - * @param item - */ - static void deleteItemFromDatabase(Context context, ItemInfo item) { - final ContentResolver cr = context.getContentResolver(); - - cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null); - } - - - /** - * Remove the contents of the specified folder from the database - */ - static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) { - final ContentResolver cr = context.getContentResolver(); - - cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); - cr.delete(LauncherSettings.Favorites.CONTENT_URI, - LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); - } - - static void deleteGestureFromDatabase(Context context, ItemInfo item) { - final ContentResolver cr = context.getContentResolver(); - - cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null); - } - - static void updateGestureInDatabase(Context context, ItemInfo item) { - final ContentValues values = new ContentValues(); - final ContentResolver cr = context.getContentResolver(); - - item.onAddToDatabase(values); - - cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null); + return (LiveFolderInfo) folderInfo; } + private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { + final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI, + new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE, + LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE }, + null, null, null); - ApplicationInfo queryGesture(Context context, String id) { - final ContentResolver contentResolver = context.getContentResolver(); - final PackageManager manager = context.getPackageManager(); - final Cursor c = contentResolver.query( - LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?", - new String[] { id }, null); + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - ApplicationInfo info = null; + // boolean changed = false; try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID); - final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE); - final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE); - final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON); - final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE); - final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE); - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE); - - String intentDescription; - Intent intent; - - if (c.moveToNext()) { - int itemType = c.getInt(itemTypeIndex); - - switch (itemType) { - case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - intentDescription = c.getString(intentIndex); - try { - intent = Intent.parseUri(intentDescription, 0); - } catch (java.net.URISyntaxException e) { - return null; - } + while (c.moveToNext()) { + try { + if (c.getInt(itemTypeIndex) != + LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + continue; + } - if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = getApplicationInfo(manager, intent, context); - } else { - info = getApplicationInfoShortcut(c, context, iconTypeIndex, - iconPackageIndex, iconResourceIndex, iconIndex); - } + final String intentUri = c.getString(intentIndex); + if (intentUri != null) { + final Intent shortcut = Intent.parseUri(intentUri, 0); + if (Intent.ACTION_MAIN.equals(shortcut.getAction())) { + final ComponentName name = shortcut.getComponent(); + if (name != null) { + final ActivityInfo activityInfo = manager.getActivityInfo(name, 0); + final String title = c.getString(titleIndex); + String label = getLabel(manager, activityInfo); - if (info == null) { - info = new ApplicationInfo(); - info.icon = manager.getDefaultActivityIcon(); - } + if (title == null || !title.equals(label)) { + final ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.TITLE, label); - info.isGesture = true; - info.title = c.getString(titleIndex); - info.intent = intent; - info.id = c.getLong(idIndex); + resolver.update( + LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, + values, "_id=?", + new String[] { String.valueOf(c.getLong(idIndex)) }); - break; + // changed = true; + } + } + } + } + } catch (URISyntaxException e) { + // Ignore + } catch (PackageManager.NameNotFoundException e) { + // Ignore } } - } catch (Exception e) { - w(LOG_TAG, "Could not load gesture with name " + id); } finally { c.close(); } - return info; + // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null); + } + + private static String getLabel(PackageManager manager, ActivityInfo activityInfo) { + String label = activityInfo.loadLabel(manager).toString(); + if (label == null) { + label = manager.getApplicationLabel(activityInfo.applicationInfo).toString(); + if (label == null) { + label = activityInfo.name; + } + } + return label; } + + private static final Collator sCollator = Collator.getInstance(); + private static final Comparator<ApplicationInfo> sComparator + = new Comparator<ApplicationInfo>() { + public final int compare(ApplicationInfo a, ApplicationInfo b) { + return sCollator.compare(a.title.toString(), b.title.toString()); + } + }; } diff --git a/src/com/android/launcher2/LiveFolderAdapter.java b/src/com/android/launcher2/LiveFolderAdapter.java index b0e9eff..05e7a6a 100644 --- a/src/com/android/launcher2/LiveFolderAdapter.java +++ b/src/com/android/launcher2/LiveFolderAdapter.java @@ -154,7 +154,8 @@ class LiveFolderAdapter extends CursorAdapter { cursor.getString(holder.iconPackageIndex)); final int id = resources.getIdentifier(resource, null, null); - icon = Utilities.createIconThumbnail(resources.getDrawable(id), mContext); + icon = Utilities.createIconThumbnail(resources.getDrawable(id), mContext, + false); mIcons.put(resource, icon); } catch (Exception e) { // Ignore diff --git a/src/com/android/launcher2/LiveFolderIcon.java b/src/com/android/launcher2/LiveFolderIcon.java index 1876f2e..d281a64 100644 --- a/src/com/android/launcher2/LiveFolderIcon.java +++ b/src/com/android/launcher2/LiveFolderIcon.java @@ -41,8 +41,8 @@ public class LiveFolderIcon extends FolderIcon { final Resources resources = launcher.getResources(); Drawable d = folderInfo.icon; if (d == null) { - d = Utilities.createIconThumbnail( - resources.getDrawable(R.drawable.ic_launcher_folder), launcher); + d = Utilities.createIconThumbnail(resources.getDrawable(R.drawable.ic_launcher_folder), + launcher, false); folderInfo.filtered = true; } icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null); diff --git a/src/com/android/launcher2/UserFolderInfo.java b/src/com/android/launcher2/UserFolderInfo.java index 40ec9e1..75b19ee 100644 --- a/src/com/android/launcher2/UserFolderInfo.java +++ b/src/com/android/launcher2/UserFolderInfo.java @@ -43,7 +43,7 @@ class UserFolderInfo extends FolderInfo { } /** - * Remove an app or shortcut + * Remove an app or shortcut. Does not change the DB. * * @param item */ diff --git a/src/com/android/launcher2/Utilities.java b/src/com/android/launcher2/Utilities.java index 9e715d1..53e42e3 100644 --- a/src/com/android/launcher2/Utilities.java +++ b/src/com/android/launcher2/Utilities.java @@ -78,82 +78,101 @@ final class Utilities { * The size of the thumbnail is defined by the dimension * android.R.dimen.launcher_application_icon_size. * - * This method is not thread-safe and should be invoked on the UI thread only. - * * @param icon The icon to get a thumbnail of. * @param context The application's context. + * @param forceBitmap If this is true, the drawable will always be redrawn + * into a new bitmap if it isn't already a BitmapDrawable or a + * FastBitmapDrawable. * * @return A thumbnail for the specified icon or the icon itself if the * thumbnail could not be created. */ - static Drawable createIconThumbnail(Drawable icon, Context context) { - if (sIconWidth == -1) { - final Resources resources = context.getResources(); - sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size); - } - - int width = sIconWidth; - int height = sIconHeight; - - float scale = 1.0f; - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } else if (icon instanceof BitmapDrawable) { - // Ensure the bitmap has a density. - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); + static Drawable createIconThumbnail(Drawable icon, Context context, boolean forceBitmap) { + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + final Resources resources = context.getResources(); + sIconWidth = (int)resources.getDimension(android.R.dimen.app_icon_size); + sIconHeight = sIconWidth; } - } - int iconWidth = icon.getIntrinsicWidth(); - int iconHeight = icon.getIntrinsicHeight(); - if (width > 0 && height > 0) { - if (width < iconWidth || height < iconHeight || scale != 1.0f) { - final float ratio = (float) iconWidth / iconHeight; - - if (iconWidth > iconHeight) { - height = (int) (width / ratio); - } else if (iconHeight > iconWidth) { - width = (int) (height * ratio); + int width = sIconWidth; + int height = sIconHeight; + + float scale = 1.0f; + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); } - - final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? - Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; - final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); - final Canvas canvas = sCanvas; - canvas.setBitmap(thumb); - // Copy the old bounds to restore them later - // If we were to do oldBounds = icon.getBounds(), - // the call to setBounds() that follows would - // change the same instance and we would lose the - // old bounds - sOldBounds.set(icon.getBounds()); - final int x = (sIconWidth - width) / 2; - final int y = (sIconHeight - height) / 2; - icon.setBounds(x, y, x + width, y + height); - icon.draw(canvas); - icon.setBounds(sOldBounds); - icon = new FastBitmapDrawable(thumb); - } else if (iconWidth < width && iconHeight < height) { + } + int iconWidth = icon.getIntrinsicWidth(); + int iconHeight = icon.getIntrinsicHeight(); + + if (iconWidth > 0 && iconWidth > 0) { + if (width < iconWidth || height < iconHeight || scale != 1.0f) { + final float ratio = (float) iconWidth / iconHeight; + + if (iconWidth > iconHeight) { + height = (int) (width / ratio); + } else if (iconHeight > iconWidth) { + width = (int) (height * ratio); + } + + final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? + Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); + final Canvas canvas = sCanvas; + canvas.setBitmap(thumb); + // Copy the old bounds to restore them later + // If we were to do oldBounds = icon.getBounds(), + // the call to setBounds() that follows would + // change the same instance and we would lose the + // old bounds + sOldBounds.set(icon.getBounds()); + final int x = (sIconWidth - width) / 2; + final int y = (sIconHeight - height) / 2; + icon.setBounds(x, y, x + width, y + height); + icon.draw(canvas); + icon.setBounds(sOldBounds); + icon = new FastBitmapDrawable(thumb); + } else if (iconWidth < width && iconHeight < height) { + final Bitmap.Config c = Bitmap.Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); + final Canvas canvas = sCanvas; + canvas.setBitmap(thumb); + sOldBounds.set(icon.getBounds()); + final int x = (width - iconWidth) / 2; + final int y = (height - iconHeight) / 2; + icon.setBounds(x, y, x + iconWidth, y + iconHeight); + icon.draw(canvas); + icon.setBounds(sOldBounds); + icon = new FastBitmapDrawable(thumb); + } + } + + if (forceBitmap) { + // no intrinsic size --> use default size + int w = sIconWidth; + int h = sIconHeight; final Bitmap.Config c = Bitmap.Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); + final Bitmap thumb = Bitmap.createBitmap(roundToPow2(w), roundToPow2(h), c); final Canvas canvas = sCanvas; canvas.setBitmap(thumb); sOldBounds.set(icon.getBounds()); - final int x = (width - iconWidth) / 2; - final int y = (height - iconHeight) / 2; - icon.setBounds(x, y, x + iconWidth, y + iconHeight); + icon.setBounds(0, 0, w, h); icon.draw(canvas); icon.setBounds(sOldBounds); icon = new FastBitmapDrawable(thumb); } - } - return icon; + return icon; + } } /** @@ -161,8 +180,6 @@ final class Utilities { * The size of the thumbnail is defined by the dimension * android.R.dimen.launcher_application_icon_size. * - * This method is not thread-safe and should be invoked on the UI thread only. - * * @param bitmap The bitmap to get a thumbnail of. * @param context The application's context. * @@ -170,42 +187,44 @@ final class Utilities { * thumbnail could not be created. */ static Bitmap createBitmapThumbnail(Bitmap bitmap, Context context) { - if (sIconWidth == -1) { - final Resources resources = context.getResources(); - sIconWidth = sIconHeight = (int) resources.getDimension( - android.R.dimen.app_icon_size); - } + synchronized (sCanvas) { // we share the statics :-( + if (sIconWidth == -1) { + final Resources resources = context.getResources(); + sIconWidth = sIconHeight = (int) resources.getDimension( + android.R.dimen.app_icon_size); + } - int width = sIconWidth; - int height = sIconHeight; + int width = sIconWidth; + int height = sIconHeight; - final int bitmapWidth = bitmap.getWidth(); - final int bitmapHeight = bitmap.getHeight(); + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); - if (width > 0 && height > 0 && (width < bitmapWidth || height < bitmapHeight)) { - final float ratio = (float) bitmapWidth / bitmapHeight; + if (width > 0 && height > 0 && (width < bitmapWidth || height < bitmapHeight)) { + final float ratio = (float) bitmapWidth / bitmapHeight; - if (bitmapWidth > bitmapHeight) { - height = (int) (width / ratio); - } else if (bitmapHeight > bitmapWidth) { - width = (int) (height * ratio); + if (bitmapWidth > bitmapHeight) { + height = (int) (width / ratio); + } else if (bitmapHeight > bitmapWidth) { + width = (int) (height * ratio); + } + + final Bitmap.Config c = (width == sIconWidth && height == sIconHeight) ? + bitmap.getConfig() : Bitmap.Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); + final Canvas canvas = sCanvas; + final Paint paint = sPaint; + canvas.setBitmap(thumb); + paint.setDither(false); + paint.setFilterBitmap(true); + sBounds.set((sIconWidth - width) / 2, (sIconHeight - height) / 2, width, height); + sOldBounds.set(0, 0, bitmapWidth, bitmapHeight); + canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint); + return thumb; } - final Bitmap.Config c = (width == sIconWidth && height == sIconHeight) ? - bitmap.getConfig() : Bitmap.Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); - final Canvas canvas = sCanvas; - final Paint paint = sPaint; - canvas.setBitmap(thumb); - paint.setDither(false); - paint.setFilterBitmap(true); - sBounds.set((sIconWidth - width) / 2, (sIconHeight - height) / 2, width, height); - sOldBounds.set(0, 0, bitmapWidth, bitmapHeight); - canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint); - return thumb; + return bitmap; } - - return bitmap; } static class BubbleText { @@ -261,7 +280,6 @@ final class Utilities { mFirstLineY = (int)(leading + ascent + 0.5f); mLineHeight = (int)(leading + ascent + descent + 0.5f); - roundToPow2(64); mBitmapWidth = roundToPow2((int)(mBubbleRect.width() + 0.5f)); mBitmapHeight = roundToPow2((int)((MAX_LINES * mLineHeight) + leading + 0.5f)); diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index 759deb6..8b00483 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.ComponentName; import android.content.res.TypedArray; +import android.content.pm.PackageManager; import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.Rect; @@ -100,7 +101,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag private int[] mTempEstimate = new int[2]; private boolean mAllowLongPress; - private boolean mLocked; private int mTouchSlop; private int mMaximumVelocity; @@ -642,10 +642,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - Log.d(Launcher.LOG_TAG, "Workspace onIntercept " + ev + " mLocked=" + mLocked - + " mLauncher.isDrawerDown()=" + mLauncher.isDrawerDown()); - if (mLocked || !mLauncher.isDrawerDown()) { - Log.d(Launcher.LOG_TAG, "returning false"); + if (mLauncher.isWorkspaceLocked() || !mLauncher.isDrawerDown()) { return false; // We don't want the events. Let them fall through to the all apps view. } @@ -755,9 +752,7 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag @Override public boolean onTouchEvent(MotionEvent ev) { - Log.d(Launcher.LOG_TAG, "Workspace onTouchEvent " + ev); - - if (mLocked || !mLauncher.isDrawerDown()) { + if (mLauncher.isWorkspaceLocked() || !mLauncher.isDrawerDown()) { return false; // We don't want the events. Let them fall through to the all apps view. } @@ -990,8 +985,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag cellLayout.onDropChild(view, mTargetCell); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - final LauncherModel model = Launcher.getModel(); - model.addDesktopItem(info); LauncherModel.addOrMoveItemInDatabase(mLauncher, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY); } @@ -1088,7 +1081,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag mDragController.removeDropTarget((DropTarget)mDragInfo.cell); } final Object tag = mDragInfo.cell.getTag(); - Launcher.getModel().removeDesktopItem((ItemInfo) tag); } } else { if (mDragInfo != null) { @@ -1187,24 +1179,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } /** - * Unlocks the SlidingDrawer so that touch events are processed. - * - * @see #lock() - */ - public void unlock() { - mLocked = false; - } - - /** - * Locks the SlidingDrawer so that touch events are ignores. - * - * @see #unlock() - */ - public void lock() { - mLocked = true; - } - - /** * @return True is long presses are still allowed for the current touch */ public boolean allowLongPress() { @@ -1221,7 +1195,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag void removeShortcutsForPackage(String packageName) { final ArrayList<View> childrenToRemove = new ArrayList<View>(); - final LauncherModel model = Launcher.getModel(); final int count = getChildCount(); for (int i = 0; i < count; i++) { @@ -1244,7 +1217,6 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null && packageName.equals(name.getPackageName())) { - model.removeDesktopItem(info); LauncherModel.deleteItemFromDatabase(mLauncher, info); childrenToRemove.add(view); } @@ -1293,6 +1265,8 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag } void updateShortcutsForPackage(String packageName) { + final PackageManager pm = mLauncher.getPackageManager(); + final int count = getChildCount(); for (int i = 0; i < count; i++) { final CellLayout layout = (CellLayout) getChildAt(i); @@ -1311,11 +1285,10 @@ public class Workspace extends ViewGroup implements DropTarget, DragSource, Drag Intent.ACTION_MAIN.equals(intent.getAction()) && name != null && packageName.equals(name.getPackageName())) { - final Drawable icon = Launcher.getModel().getApplicationInfoIcon( - mLauncher.getPackageManager(), info); + final Drawable icon = AppInfoCache.getIconDrawable(pm, info); if (icon != null && icon != info.icon) { info.icon.setCallback(null); - info.icon = Utilities.createIconThumbnail(icon, mContext); + info.icon = Utilities.createIconThumbnail(icon, mContext, false); info.filtered = true; ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null, info.icon, null, null); |