/* * 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.fruit.launcher; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; 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.os.Parcelable; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; import android.os.Process; import android.os.SystemClock; import android.provider.BaseColumns; import android.provider.LiveFolders; 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 com.fruit.launcher.LauncherSettings.Applications; import com.fruit.launcher.LauncherSettings.BaseLauncherColumns; import com.fruit.launcher.LauncherSettings.Favorites; import com.fruit.launcher.setting.SettingUtils; import com.fruit.launcher.theme.ThemeManager; import com.fruit.launcher.theme.ThemeUtils; /** * Maintains in-memory state of the Launcher. It is expected that there should * be only one LauncherModel object held in a static. Also provide APIs for * updating the database state for the Launcher. */ public class LauncherModel extends BroadcastReceiver { static final boolean DEBUG_LOADERS = true; static final boolean PROFILE_LOADERS = false; static final String TAG = "Launcher.Model"; private int mBatchSize; // 0 is all apps at once private int mAllAppsLoadDelay; // milliseconds between batches private final LauncherApplication mApp; private final Object mLock = new Object(); private DeferredHandler mHandler = new DeferredHandler(); private Loader mLoader = new Loader(); // We start off with everything not loaded. After that, we assume that // our monitoring of the package manager provides all updates and we never // need to do a requery. These are only ever touched from the loader thread. private boolean mWorkspaceLoaded; private boolean mAllAppsLoaded; private boolean mBeforeFirstLoad = true; // only access this from main // thread private WeakReference<Callbacks> mCallbacks; private final Object mAllAppsListLock = new Object(); private AllAppsList mAllAppsList; private IconCache mIconCache; private Bitmap mDefaultIcon; public interface Callbacks { public int getCurrentWorkspaceScreen(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); public void bindDockBarItems(ArrayList<ItemInfo> items); public void bindFolders(HashMap<Long, FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindCustomAppWidget(CustomAppWidgetInfo info); public void bindAllApplications(ArrayList<ApplicationInfo> apps); public void bindAppsAdded(ArrayList<ApplicationInfo> apps); public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); public void bindAppsRemoved(ArrayList<ApplicationInfo> apps); public boolean isAllAppsVisible(); public void removePackage(ArrayList<ApplicationInfo> apps); public void addPackage(ArrayList<ApplicationInfo> apps); public void updateAllApps(); public void removeThemePackage(ArrayList<String> apps); public int findEmptyCell(int[] xy); public void setWorkspaceLoading(boolean loading); public void bindAppsAddedAfterInstall(ArrayList<ApplicationInfo> apps); } LauncherModel(LauncherApplication app, IconCache iconCache) { mApp = app; mAllAppsList = new AllAppsList(iconCache); mIconCache = iconCache; mDefaultIcon = Utilities.createIconBitmap(app.getPackageManager() .getDefaultActivityIcon(), app); mAllAppsLoadDelay = app.getResources().getInteger( R.integer.config_allAppsBatchLoadDelay); mBatchSize = app.getResources().getInteger( R.integer.config_allAppsBatchSize); } public Bitmap getFallbackIcon() { return Bitmap.createBitmap(mDefaultIcon); } /** * 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(Favorites.CONTAINER, item.container); values.put(Favorites.CELLX, item.cellX); values.put(Favorites.CELLY, item.cellY); values.put(Favorites.SCREEN, item.screen); values.put(BaseLauncherColumns.ORDERID, item.orderId); cr.update(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(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; } /** * 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(Favorites.CONTENT_URI, null, "_id=? and (itemType=? or itemType=?)", new String[] { String.valueOf(id), String.valueOf(Favorites.ITEM_TYPE_USER_FOLDER), String.valueOf(Favorites.ITEM_TYPE_LIVE_FOLDER) }, null); try { if (c.moveToFirst()) { final int itemTypeIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ITEM_TYPE); final int titleIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.TITLE); final int containerIndex = c .getColumnIndexOrThrow(Favorites.CONTAINER); final int screenIndex = c .getColumnIndexOrThrow(Favorites.SCREEN); final int cellXIndex = c.getColumnIndexOrThrow(Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(Favorites.CELLY); FolderInfo folderInfo = null; switch (c.getInt(itemTypeIndex)) { case Favorites.ITEM_TYPE_USER_FOLDER: folderInfo = findOrMakeUserFolder(folderList, id); break; case Favorites.ITEM_TYPE_LIVE_FOLDER: folderInfo = findOrMakeLiveFolder(folderList, 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); return folderInfo; } } finally { c.close(); } 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. */ 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 ? Favorites.CONTENT_URI : Favorites.CONTENT_URI_NO_NOTIFICATION, values); if (result != null) { item.id = Integer.parseInt(result.getPathSegments().get(1)); } } /** * 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(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(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(Favorites.getContentUri(info.id, false), null, null); cr.delete(Favorites.CONTENT_URI, Favorites.CONTAINER + "=" + info.id, null); } static int queryDBCountByPageIndex(Context context, int pageIndex, ArrayList<String> titleArray){ int result = 0; final ContentResolver cr = context.getContentResolver(); final Cursor cursor = cr.query(Favorites.CONTENT_URI, new String[] {Favorites.TITLE, Favorites.SCREEN, Favorites.CONTAINER}, "screen=? and container=?", new String[] {Integer.toString(pageIndex), Integer.toString(Favorites.CONTAINER_DESKTOP)}, null); try { if (cursor!=null){ result = cursor.getCount(); while(cursor.moveToNext()){ final String title = cursor.getString(0); titleArray.add(title); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * Set this as the current Launcher activity object for the loader. */ public void initialize(Callbacks callbacks) { synchronized (mLock) { mCallbacks = new WeakReference<Callbacks>(callbacks); } } public void startLoader(Context context, boolean isLaunching) { //if (!mWorkspaceLoaded) mLoader.startLoader(context, isLaunching); } public void stopLoader() { mLoader.stopLoader(); } // public void createShortcutEx(Context context, Intent intent, String packageName) { // LauncherApplication app = (LauncherApplication) context; // final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); // mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); // final PackageManager packageManager = context.getPackageManager(); // List<ResolveInfo> apps = null; // apps = packageManager.queryIntentActivities(mainIntent, 0); // // if (apps.size() == 0) { // return; // } // //// Collections.sort(apps, new ResolveInfo.DisplayNameComparator( //// packageManager)); // // for (int j = 0; j < apps.size(); j++) { // // This builds the icon bitmaps. // ResolveInfo info = apps.get(j); // // final android.content.pm.ApplicationInfo appInfo = info.activityInfo.applicationInfo; // // //String intentInfo = ""; // String infoName = info.activityInfo.name; // // if (!appInfo.packageName.equals(packageName)) // continue; // // Intent shortcutIntent = new Intent(InstallShortcutReceiver.ACTION_INSTALL_SHORTCUT); // // shortcutIntent.putExtra("duplicate", false); // // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.loadLabel(packageManager).toString()); // // ComponentName cn = new ComponentName(appInfo.packageName, infoName); // final IconCache ic = app.getIconCache(); // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, ic.getIcon(cn, info)); //// app.getResources().get //// Parcelable icon = Intent.ShortcutIconResource.fromContext(app.getApplicationContext(), packageName+":drawable/icon"); //// shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, packageName+":drawable/icon"); // // Intent intent2 = new Intent(); // intent2.setComponent(cn); // intent2.setAction("android.intent.action.MAIN"); // intent2.addCategory("android.intent.category.LAUNCHER"); // intent2.setFlags(0x10200000); // // intent2.setAction(intentInfo); // // // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent2); // // context.sendBroadcast(shortcutIntent); // break; // } // } // public void createShortcutEx2(Context context, Intent intent, String packageName) { // LauncherApplication app = (LauncherApplication) context; // PackageManager pm = context.getPackageManager(); // // //Intent shortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); // Intent shortcutIntent = new Intent(InstallShortcutReceiver.ACTION_INSTALL_SHORTCUT); // //Intent shortcutIntent = intent; // //intent.setAction(InstallShortcutReceiver.ACTION_INSTALL_SHORTCUT); // //shortcutIntent.putExtra("duplicate", false); // // Intent intent2 = new Intent(); // intent2.setComponent(new ComponentName(packageName, packageName)); // intent2.setAction("android.intent.action.MAIN"); // intent2.addCategory("android.intent.category.LAUNCHER"); // intent2.setFlags(0x10200000); // // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent2); // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, packageName); // shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, app.getIconCache().getIcon(intent)); // //shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); // // context.sendBroadcast(shortcutIntent); // } /** * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED * and ACTION_PACKAGE_CHANGED. */ @Override public void onReceive(Context context, Intent intent) { // Use the app as the context. context = mApp; ArrayList<ApplicationInfo> added = null; ArrayList<ApplicationInfo> removed = null; ArrayList<ApplicationInfo> modified = null; ArrayList<String> themeRemoved = null; final String action = intent.getAction(); if (mBeforeFirstLoad) { // If we haven't even loaded yet, don't bother, since we'll just // pick up the changes. if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { Log.e(TAG, "onReceive, Before First Load, ACTION_EXTERNAL_APPLICATIONS_AVAILABLE"); } else { Log.e(TAG, "Before First Load, action=" + action + ", retrun!"); return; } } synchronized (mAllAppsListLock) { Log.e(TAG, "LauncherModel onReceive aciton =" + action); if (Intent.ACTION_PACKAGE_CHANGED.equals(action) || Intent.ACTION_PACKAGE_REMOVED.equals(action) || Intent.ACTION_PACKAGE_ADDED.equals(action)) { final String packageName = intent.getData() .getSchemeSpecificPart(); final boolean replacing = intent.getBooleanExtra( Intent.EXTRA_REPLACING, false); if (packageName == null || packageName.length() == 0) { // they sent us a bad intent return; } if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { mAllAppsList.updatePackage(context, packageName); } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if (!replacing) { mAllAppsList.removePackage(packageName); } if (packageName != null && packageName .startsWith(ThemeUtils.THEME_PACKAGE_TOKEN)) { themeRemoved = new ArrayList<String>(); themeRemoved.add(new String(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 (Intent.ACTION_PACKAGE_ADDED.equals(action)) { if (!replacing) { mAllAppsList.addPackage(context, packageName); } else { mAllAppsList.updatePackage(context, packageName); } } if (mAllAppsList.added.size() > 0) { added = mAllAppsList.added; mAllAppsList.added = new ArrayList<ApplicationInfo>(); } if (mAllAppsList.removed.size() > 0) { removed = mAllAppsList.removed; mAllAppsList.removed = new ArrayList<ApplicationInfo>(); for (ApplicationInfo info : removed) { mIconCache.remove(info.intent.getComponent()); } } if (mAllAppsList.modified.size() > 0) { modified = mAllAppsList.modified; mAllAppsList.modified = new ArrayList<ApplicationInfo>(); } final Callbacks callbacks = mCallbacks != null ? mCallbacks .get() : null; if (callbacks == null) { Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); return; } if (added != null) { final ArrayList<ApplicationInfo> addedFinal = added; mHandler.post(new Runnable() { @Override public void run() { callbacks.bindAppsAddedAfterInstall(addedFinal); callbacks.addPackage(addedFinal); } }); //createShortcutEx(context, intent, packageName); // yfzhao } if (modified != null) { final ArrayList<ApplicationInfo> modifiedFinal = modified; mHandler.post(new Runnable() { @Override public void run() { callbacks.bindAppsUpdated(modifiedFinal); } }); //createShortcutEx(context, intent, packageName); // yfzhao // createShortcutEx(context, intent); //yfzhao // Toast.makeText(context, // context.getString(R.string.app_updated), // Toast.LENGTH_SHORT).show(); } if (removed != null) { final ArrayList<ApplicationInfo> removedFinal = removed; mHandler.post(new Runnable() { @Override public void run() { callbacks.removePackage(removedFinal); callbacks.bindAppsRemoved(removedFinal); } }); } if (themeRemoved != null) { final ArrayList<String> removedFinal = themeRemoved; mHandler.post(new Runnable() { @Override public void run() { callbacks.removeThemePackage(removedFinal); } }); } } else { final Callbacks callbacks = mCallbacks != null ? mCallbacks .get() : null; if (callbacks == null) { Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); //return; } if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE .equals(action)) { String packages[] = intent .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (DEBUG_LOADERS) Log.d(TAG, "ACTION_EXTERNAL_APPLICATIONS_AVAILABLE, size=" + packages.length); if (packages == null || packages.length == 0) { return; } if (DEBUG_LOADERS) { for (String app : packages) { Log.d(TAG, "package:" + app); } } synchronized (this) { mAllAppsLoaded = mWorkspaceLoaded = false; } Log.d(TAG, "onReceive(), startLoader,false, Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE"); callbacks.setWorkspaceLoading(true); startLoader(context, false); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE .equals(action)) { String packages[] = intent .getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (packages == null || packages.length == 0) { return; } synchronized (this) { mAllAppsLoaded = mWorkspaceLoaded = false; } Log.d(TAG, "onReceive(), startLoader,false, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE"); callbacks.setWorkspaceLoading(true); startLoader(context, false); } } } } public class Loader { private static final int ITEMS_CHUNK = 6; private LoaderThread mLoaderThread; final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); final ArrayList<ItemInfo> mDockBarItems = new ArrayList<ItemInfo>(); final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>(); final ArrayList<CustomAppWidgetInfo> mCustomAppWidgets = new ArrayList<CustomAppWidgetInfo>(); final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>(); /** * Call this from the ui thread so the handler is initialized on the * correct thread. */ public Loader() { } public void startLoader(Context context, boolean isLaunching) { synchronized (mLock) { if (DEBUG_LOADERS) { 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 != null && 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(); } mHandler.cancel(); mLoaderThread = new LoaderThread(context, oldThread, isLaunching); mLoaderThread.start(); } } } public void stopLoader() { synchronized (mLock) { if (mLoaderThread != null) { mLoaderThread.stopLocked(); } } } /** * 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 mLoadAndBindStepFinished; LoaderThread(Context context, Thread waitThread, boolean isLaunching) { mContext = context; mWaitThread = waitThread; mIsLaunching = isLaunching; } boolean isLaunching() { return mIsLaunching; } /** * 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(); done = true; } catch (InterruptedException ex) { // Ignore } } mWaitThread = null; } } private void loadAndBindWorkspace() { // Load the workspace // Other other threads can unset mWorkspaceLoaded, so atomically // set it, // and then if they unset it, or we unset it because of // mStopped, it will // be unset. boolean loaded; synchronized (this) { loaded = mWorkspaceLoaded; mWorkspaceLoaded = true; } // For now, just always reload the workspace. It's ~100 ms vs. // the // binding which takes many hundreds of ms. // We can reconsider. if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindWorkspace loaded=" + loaded); } // if (true || !loaded) { if (true) { loadWorkspace(); if (mStopped) { mWorkspaceLoaded = false; return; } } // Bind the workspace bindWorkspace(); } @SuppressWarnings("unused") private void waitForIdle() { // 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) { final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock .uptimeMillis() : 0; mHandler.postIdle(new Runnable() { @Override public void run() { synchronized (LoaderThread.this) { mLoadAndBindStepFinished = true; if (DEBUG_LOADERS) { Log.d(TAG, "done with previous binding step"); } LoaderThread.this.notify(); } } }); while (!mStopped && !mLoadAndBindStepFinished) { try { this.wait(); } catch (InterruptedException ex) { // Ignore } } if (DEBUG_LOADERS) { Log.d(TAG, "waited " + (SystemClock.uptimeMillis() - workspaceWaitTime) + "ms for previous step to finish binding"); } } } @Override public void run() { waitForOtherThread(); // Optimize for end-user experience: if the Launcher is up and // // running with the // All Apps interface in the foreground, load All Apps first. // Otherwise, load the // workspace first (default). final Callbacks cbk = mCallbacks.get(); final boolean loadWorkspaceFirst = cbk != null ? (!cbk .isAllAppsVisible()) : true; // 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); } if (PROFILE_LOADERS) { android.os.Debug .startMethodTracing("/sdcard/launcher-loaders"); } if (loadWorkspaceFirst) { if (DEBUG_LOADERS) { Log.d(TAG, "step 1: loading workspace"); } loadAndBindWorkspace(); } else { if (DEBUG_LOADERS) { Log.d(TAG, "step 1: special: loading all apps"); } loadAndBindAllApps(); } // Whew! Hard work done. synchronized (mLock) { if (mIsLaunching) { android.os.Process .setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } // second step if (loadWorkspaceFirst) { if (DEBUG_LOADERS) { Log.d(TAG, "step 2: loading all apps"); } loadAndBindAllApps(); } else { if (DEBUG_LOADERS) { Log.d(TAG, "step 2: special: loading workspace"); } loadAndBindWorkspace(); } // 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; } if (PROFILE_LOADERS) { android.os.Debug.stopMethodTracing(); } // Trigger a gc to try to clean up after the stuff is done, // since the // renderscript allocations aren't charged to the java heap. mHandler.post(new Runnable() { @Override public void run() { System.gc(); } }); } public void stopLocked() { synchronized (LoaderThread.this) { mStopped = true; mHandler.cancel(); this.notify(); } } /** * Gets the callbacks object. If we've been stopped, or if the * launcher object has somehow been garbage collected, return null * instead. Pass in the Callbacks object that was around when the * deferred message was scheduled, and if there's a new Callbacks * object around then also return null. This will save us from * calling onto it with data that will be ignored. */ Callbacks tryGetCallbacks(Callbacks oldCallbacks) { synchronized (mLock) { if (mStopped) { return null; } if (mCallbacks == null) { return null; } final Callbacks callbacks = mCallbacks.get(); if (callbacks != oldCallbacks) { return null; } if (callbacks == null) { Log.w(TAG, "no mCallbacks"); return null; } return callbacks; } } // check & update map of what's occupied; used to discard // overlapping/invalid items private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item, Context context) { boolean result = checkItemPlacement(occupied, item); if (!result){ deleteItemFromDatabase(context, item); } return result; } // check & update map of what's occupied; used to discard // overlapping/invalid items private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) { if (item.container != Favorites.CONTAINER_DESKTOP) { return true; } for (int x = item.cellX; x < (item.cellX + item.spanX); x++) { for (int y = item.cellY; y < (item.cellY + item.spanY); y++) { if (occupied[item.screen][x][y] != null) { Log.e(TAG, "Error loading shortcut " + item + " into cell (" + item.screen + ":" + x + "," + y + ") occupied by " + occupied[item.screen][x][y]); return false; } } } for (int x = item.cellX; x < (item.cellX + item.spanX); x++) { for (int y = item.cellY; y < (item.cellY + item.spanY); y++) { occupied[item.screen][x][y] = item; } } return true; } private void checkScreenCount(ContentResolver cr, Context context){ int screen = SettingUtils.mScreenCount; final Cursor cursor = cr.query(Favorites.CONTENT_URI, new String[] {Favorites.SCREEN}, null, null, Favorites.SCREEN + " DESC"); try { if (cursor != null && cursor.moveToFirst()) { int maxScreenIndex = cursor.getInt(0);//cursor.getColumnIndexOrThrow(Favorites.SCREEN); if (maxScreenIndex > SettingUtils.MAX_SCREEN_COUNT-1) maxScreenIndex = SettingUtils.MAX_SCREEN_COUNT-1; if (maxScreenIndex>=screen){ SettingUtils.mScreenCount=maxScreenIndex+1; SettingUtils.saveScreenSettings(context); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); SettingUtils.mScreenCount=screen; } finally{ cursor.close(); } } private void loadWorkspace() { final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final Context context = mContext; final ContentResolver contentResolver = context .getContentResolver(); final PackageManager manager = context.getPackageManager(); final AppWidgetManager widgets = AppWidgetManager .getInstance(context); final boolean isSafeMode = manager.isSafeMode(); mItems.clear(); mDockBarItems.clear(); mAppWidgets.clear(); mCustomAppWidgets.clear(); mFolders.clear(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); final Cursor c = contentResolver.query(Favorites.CONTENT_URI, null, null, null, null); checkScreenCount(contentResolver, context); final ItemInfo occupied[][][] = new ItemInfo[SettingUtils.mScreenCount][Launcher.NUMBER_CELLS_X][Launcher.NUMBER_CELLS_Y]; try { final int idIndex = c .getColumnIndexOrThrow(BaseColumns._ID); final int intentIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.INTENT); final int titleIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.TITLE); final int iconTypeIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ICON_TYPE); final int iconIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ICON); final int iconPackageIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ICON_PACKAGE); final int iconResourceIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ICON_RESOURCE); final int containerIndex = c .getColumnIndexOrThrow(Favorites.CONTAINER); final int orderIdIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ORDERID); final int itemTypeIndex = c .getColumnIndexOrThrow(BaseLauncherColumns.ITEM_TYPE); final int appWidgetIdIndex = c .getColumnIndexOrThrow(Favorites.APPWIDGET_ID); final int screenIndex = c .getColumnIndexOrThrow(Favorites.SCREEN); final int cellXIndex = c .getColumnIndexOrThrow(Favorites.CELLX); final int cellYIndex = c .getColumnIndexOrThrow(Favorites.CELLY); final int spanXIndex = c .getColumnIndexOrThrow(Favorites.SPANX); final int spanYIndex = c .getColumnIndexOrThrow(Favorites.SPANY); final int uriIndex = c.getColumnIndexOrThrow(Favorites.URI); final int displayModeIndex = c .getColumnIndexOrThrow(Favorites.DISPLAY_MODE); ShortcutInfo info; String intentDescription; LauncherAppWidgetInfo appWidgetInfo; int container; long id; Intent intent; while (!mStopped && c.moveToNext()) { try { int itemType = c.getInt(itemTypeIndex); switch (itemType) { case BaseLauncherColumns.ITEM_TYPE_APPLICATION: case BaseLauncherColumns.ITEM_TYPE_SHORTCUT: intentDescription = c.getString(intentIndex); try { intent = Intent.parseUri(intentDescription, 0); } catch (URISyntaxException e) { continue; } if (itemType == BaseLauncherColumns.ITEM_TYPE_APPLICATION) { info = getShortcutInfo(manager, intent, context, c, iconIndex, titleIndex); } else { info = getShortcutInfo(c, context, iconTypeIndex, iconPackageIndex, iconResourceIndex, iconIndex, titleIndex); } if (info != null) { updateSavedIcon(context, info, c, iconIndex); 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); info.orderId = c.getInt(orderIdIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, info)) { break; } switch (container) { case Favorites.CONTAINER_DESKTOP: mItems.add(info); break; case Favorites.CONTAINER_DOCKBAR: mDockBarItems.add(info); break; default: // Item is in a user folder UserFolderInfo folderInfo = findOrMakeUserFolder( mFolders, container); folderInfo.add(info); break; } } else { // Failed to load the shortcut, probably // because the // activity manager couldn't resolve it // (maybe the app // was uninstalled), or the db row was // somehow screwed up. // Delete it. id = c.getLong(idIndex); Log.e(TAG, "Error loading shortcut " + id + ", removing it"); contentResolver.delete( Favorites.getContentUri(id, false), null, null); } break; case Favorites.ITEM_TYPE_USER_FOLDER: id = c.getLong(idIndex); UserFolderInfo folderInfo = findOrMakeUserFolder( mFolders, id); folderInfo.title = c.getString(titleIndex); 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); // check & update map of what's occupied if (!checkItemPlacement(occupied, folderInfo)) { break; } switch (container) { case Favorites.CONTAINER_DESKTOP: mItems.add(folderInfo); break; } mFolders.put(folderInfo.id, folderInfo); break; case Favorites.ITEM_TYPE_LIVE_FOLDER: id = c.getLong(idIndex); Uri uri = Uri.parse(c.getString(uriIndex)); // Make sure the live folder exists final ProviderInfo providerInfo = context .getPackageManager() .resolveContentProvider( uri.getAuthority(), 0); if (providerInfo == null && !isSafeMode) { itemsToRemove.add(id); } else { LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder( mFolders, 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 } } liveFolderInfo.title = c .getString(titleIndex); liveFolderInfo.id = id; liveFolderInfo.uri = uri; container = c.getInt(containerIndex); liveFolderInfo.container = container; liveFolderInfo.screen = c .getInt(screenIndex); liveFolderInfo.cellX = c.getInt(cellXIndex); liveFolderInfo.cellY = c.getInt(cellYIndex); liveFolderInfo.baseIntent = intent; liveFolderInfo.displayMode = c .getInt(displayModeIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, liveFolderInfo)) { break; } loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex, iconResourceIndex, liveFolderInfo); switch (container) { case Favorites.CONTAINER_DESKTOP: mItems.add(liveFolderInfo); break; } mFolders.put(liveFolderInfo.id, liveFolderInfo); } break; case Favorites.ITEM_TYPE_APPWIDGET: // Read all Launcher-specific widget details int appWidgetId = c.getInt(appWidgetIdIndex); id = c.getLong(idIndex); final AppWidgetProviderInfo provider = widgets .getAppWidgetInfo(appWidgetId); if (!isSafeMode && (provider == null || provider.provider == null || provider.provider .getPackageName() == null)) { Log.e(TAG, "Deleting widget that isn't installed anymore: id=" + id + " appWidgetId=" + appWidgetId); itemsToRemove.add(id); } else { appWidgetInfo = new LauncherAppWidgetInfo( appWidgetId); appWidgetInfo.id = id; 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 != Favorites.CONTAINER_DESKTOP) { Log.e(TAG, "Widget found where container " + "!= CONTAINER_DESKTOP -- ignoring!"); continue; } appWidgetInfo.container = c .getInt(containerIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, appWidgetInfo, context)) { break; } mAppWidgets.add(appWidgetInfo); } break; case Favorites.ITEM_TYPE_WIDGET_LOCK_SCREEN: case Favorites.ITEM_TYPE_WIDGET_CLEAN_MEMORY: CustomAppWidgetInfo customAppWidgetInfo = CustomAppWidgetInfo .getWidgetInfoByType(itemType); customAppWidgetInfo.id = c.getLong(idIndex); customAppWidgetInfo.screen = c .getInt(screenIndex); customAppWidgetInfo.cellX = c .getInt(cellXIndex); customAppWidgetInfo.cellY = c .getInt(cellYIndex); container = c.getInt(containerIndex); if (container != Favorites.CONTAINER_DESKTOP) { Log.e(TAG, "Widget found where container " + "!= CONTAINER_DESKTOP -- ignoring!"); continue; } customAppWidgetInfo.container = container; // check & update map of what's occupied if (!checkItemPlacement(occupied, customAppWidgetInfo)) { break; } mCustomAppWidgets.add(customAppWidgetInfo); break; } } catch (Exception e) { Log.w(TAG, "Desktop items loading interrupted:", e); } } } finally { c.close(); } if (itemsToRemove.size() > 0) { ContentProviderClient client = contentResolver .acquireContentProviderClient(Favorites.CONTENT_URI); // Remove dead items for (long id : itemsToRemove) { if (DEBUG_LOADERS) { Log.d(TAG, "Removed id = " + id); } // Don't notify content observers try { client.delete(Favorites.getContentUri(id, false), null, null); } catch (RemoteException e) { Log.w(TAG, "Could not remove id = " + id); } } } if (DEBUG_LOADERS) { Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis() - t) + "ms"); Log.d(TAG, "workspace layout: "); for (int y = 0; y < Launcher.NUMBER_CELLS_Y; y++) { String line = ""; for (int s = 0; s < SettingUtils.mScreenCount; s++) { if (s > 0) { line += " | "; } for (int x = 0; x < Launcher.NUMBER_CELLS_X; x++) { line += ((occupied[s][x][y] != null) ? "#" : "."); } } Log.d(TAG, "[ " + line + " ]"); } } } /** * 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. final Callbacks oldCallbacks = mCallbacks.get(); if (oldCallbacks == 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() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); 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() { @Override public void run() { if (DEBUG_LOADERS) { Log.d(TAG, "bindWorkspace, bindItems, mStopped=" + mStopped + ", this=" + this); } Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindItems(mItems, start, start + chunkSize); } } }); } // Bind Dock Bar items mHandler.post(new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindDockBarItems(mDockBarItems); } } }); // Wait until the queue goes empty. mHandler.post(new Runnable() { @Override public void run() { if (DEBUG_LOADERS) { Log.d(TAG, "Going to start binding folders soon."); } } }); // Bind folders mHandler.post(new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindFolders(mFolders); } } }); // Wait until the queue goes empty. mHandler.post(new Runnable() { @Override public void run() { if (DEBUG_LOADERS) { 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 = oldCallbacks .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() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); 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() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }); } } // Bind the LauncherHQ custom 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. N = mCustomAppWidgets.size(); // once for the current screen for (int i = 0; i < N; i++) { final CustomAppWidgetInfo widget = mCustomAppWidgets.get(i); if (widget.screen == currentScreen) { mHandler.post(new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindCustomAppWidget(widget); } } }); } } // once for the other screens for (int i = 0; i < N; i++) { final CustomAppWidgetInfo widget = mCustomAppWidgets.get(i); if (widget.screen != currentScreen) { mHandler.post(new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindCustomAppWidget(widget); } } }); } } // Tell the workspace that we're done. mHandler.post(new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.finishBindingItems(); } } }); // If we're profiling, this is the last thing in the queue. mHandler.post(new Runnable() { @Override public void run() { if (DEBUG_LOADERS) { Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis() - t) + "ms"); } } }); } private void loadAndBindAllApps() { // Other other threads can unset mAllAppsLoaded, so atomically // set it, // and then if they unset it, or we unset it because of // mStopped, it will // be unset. boolean loaded; synchronized (this) { loaded = mAllAppsLoaded; mAllAppsLoaded = true; } if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindAllApps loaded=" + loaded); } if (!loaded) { loadAllAppsByBatch(); if (mStopped) { mAllAppsLoaded = false; return; } } else { onlyBindAllApps(); } updateSlideData(mContext); } private void onlyBindAllApps() { final Callbacks oldCallbacks = mCallbacks.get(); if (oldCallbacks == null) { // This launcher has exited and nobody bothered to tell us. // Just bail. Log.w(TAG, "LoaderThread running with no launcher (onlyBindAllApps)"); return; } // shallow copy final ArrayList<ApplicationInfo> list = (ArrayList<ApplicationInfo>) mAllAppsList.data .clone(); mHandler.post(new Runnable() { @Override public void run() { final long t = SystemClock.uptimeMillis(); final Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAllApplications(list); } if (DEBUG_LOADERS) { Log.d(TAG, "bound all " + list.size() + " apps from cache in " + (SystemClock.uptimeMillis() - t) + "ms"); } } }); } private void loadAllAppsByBatch() { final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; // Don't use these two variables in any of the callback // runnables. // Otherwise we hold a reference to them. final Callbacks oldCallbacks = mCallbacks.get(); if (oldCallbacks == null) { // This launcher has exited and nobody bothered to tell us. // Just bail. Log.w(TAG, "LoaderThread running with no launcher (loadAllAppsByBatch)"); return; } final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = mContext .getPackageManager(); List<ResolveInfo> apps = null; int N = Integer.MAX_VALUE; int startIndex; int i = 0; int batchSize = -1; while (i < N && !mStopped) { synchronized (mAllAppsListLock) { if (i == 0) { // This needs to happen inside the same lock block // as when we // prepare the first batch for bindAllApplications. // Otherwise // the package changed receiver can come in and // double-add // (or miss one?). mAllAppsList.clear(); final long qiaTime = DEBUG_LOADERS ? SystemClock .uptimeMillis() : 0; apps = packageManager.queryIntentActivities( mainIntent, 0); if (DEBUG_LOADERS) { Log.d(TAG, "queryIntentActivities took " + (SystemClock.uptimeMillis() - qiaTime) + "ms"); } if (apps == null) { return; } N = apps.size(); if (DEBUG_LOADERS) { Log.d(TAG, "queryIntentActivities got " + N + " apps"); } if (N == 0) { // There are no apps?!? return; } if (mBatchSize == 0) { batchSize = N; } else { batchSize = mBatchSize; } final long sortTime = DEBUG_LOADERS ? SystemClock .uptimeMillis() : 0; Collections.sort(apps, new ResolveInfo.DisplayNameComparator( packageManager)); if (DEBUG_LOADERS) { Log.d(TAG, "sort took " + (SystemClock.uptimeMillis() - sortTime) + "ms"); } } final long t2 = DEBUG_LOADERS ? SystemClock .uptimeMillis() : 0; startIndex = i; for (int j = 0; i < N && j < batchSize; j++) { // This builds the icon bitmaps. mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache)); i++; } final boolean first = i <= batchSize; final Callbacks callbacks = tryGetCallbacks(oldCallbacks); final ArrayList<ApplicationInfo> added = mAllAppsList.added; mAllAppsList.added = new ArrayList<ApplicationInfo>(); mHandler.post(new Runnable() { @Override public void run() { final long t = SystemClock.uptimeMillis(); if (callbacks != null) { if (first) { mBeforeFirstLoad = false; callbacks.bindAllApplications(added); } else { callbacks.bindAppsAdded(added); } if (DEBUG_LOADERS) { Log.d(TAG, "bound " + added.size() + " apps in " + (SystemClock .uptimeMillis() - t) + "ms"); } } else { Log.d(TAG, "not binding apps: no Launcher activity"); } } }); if (DEBUG_LOADERS) { Log.d(TAG, "batch of " + (i - startIndex) + " icons processed in " + (SystemClock.uptimeMillis() - t2) + "ms"); } } if (mAllAppsLoadDelay > 0 && i < N) { try { if (DEBUG_LOADERS) { Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); } Thread.sleep(mAllAppsLoadDelay); } catch (InterruptedException exc) { } } } if (DEBUG_LOADERS) { Log.d(TAG, "cached all " + N + " apps in " + (SystemClock.uptimeMillis() - t) + "ms" + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); } } private void updateSlideData(Context context) { final Callbacks oldCallbacks = mCallbacks.get(); if (oldCallbacks == null) { // This launcher has exited and nobody bothered to tell us. // Just bail. Log.w(TAG, "LoaderThread running with no launcher (loadAllAppsByBatch)"); return; } final ContentResolver cr = context.getContentResolver(); int count = removePackageFromSlideData(context, cr); final Callbacks callbacks = tryGetCallbacks(oldCallbacks); int addCount = addPackageToSlideData(context, cr, count, callbacks); Log.e(TAG, "updateSlideData, count = " + count + ", addCount=" + addCount); mHandler.post(new Runnable() { @Override public void run() { final long t = SystemClock.uptimeMillis(); if (callbacks != null) { callbacks.updateAllApps(); if (DEBUG_LOADERS) { Log.d(TAG, "updateSlideData, apps in " + (SystemClock.uptimeMillis() - t) + "ms"); } } else { Log.d(TAG, "not binding apps: no Launcher activity"); } } }); } int addPackageToSlideData(Context context, ContentResolver cr, int count, final Callbacks callbacks) { final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); int lastPos = 0; int add = 0; mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = context .getPackageManager(); List<ResolveInfo> apps = null; apps = packageManager.queryIntentActivities(mainIntent, 0); if (apps.size() == 0) { return 0; } if (apps.size() > count) { final Uri insetUri = Applications.CONTENT_URI_NO_NOTIFICATION; Cursor c = cr.query(insetUri, new String[] { BaseColumns._ID }, Applications.CONTAINER + "=?", new String[] { String .valueOf(Applications.CONTAINER_APPS) }, null); if (c != null) { lastPos = c.getCount(); c.close(); } final String selfPkgName = mContext.getPackageName(); for (int i = 0; i < apps.size(); i++) { ResolveInfo info = apps.get(i); // Do not add custom theme package to all application if (info.activityInfo.packageName .startsWith(ThemeUtils.THEME_PACKAGE_TOKEN) || info.activityInfo.packageName .equals(selfPkgName)) { continue; } c = cr.query( Applications.CONTENT_URI_NO_NOTIFICATION, new String[] { Applications.PACKAGENAME }, Applications.PACKAGENAME + "=?", new String[] { info.activityInfo.applicationInfo.packageName }, null); if (c != null && !c.moveToNext()) { ContentValues values = new ContentValues(); final android.content.pm.ApplicationInfo appInfo = info.activityInfo.applicationInfo; String intentInfo = appInfo.packageName + "|" + info.activityInfo.name; values.put(BaseLauncherColumns.TITLE, info .loadLabel(packageManager).toString()); values.put(BaseLauncherColumns.INTENT, intentInfo); values.put(Applications.CONTAINER, Applications.CONTAINER_APPS); values.put(Applications.POSITION, lastPos); values.put(BaseLauncherColumns.ITEM_TYPE, Applications.APPS_TYPE_APP); values.put(Applications.SYSAPP, 0); values.put( Applications.PACKAGENAME, info.activityInfo.applicationInfo.packageName); cr.insert(insetUri, values); Log.w(TAG, "addPackageToSlideData app=" + info.activityInfo.applicationInfo.packageName + ", position=" + (lastPos)); lastPos++; add++; //add to favor ArrayList<ApplicationInfo> addedApp = new ArrayList<ApplicationInfo>(); //ApplicationInfo aInfo = new ApplicationInfo(); addedApp.add(new ApplicationInfo(info, mIconCache)); //callbacks.bindAppsAdded(addedApp); if (addedApp != null && callbacks!=null) { final ArrayList<ApplicationInfo> addedFinal = addedApp; mHandler.post(new Runnable() { @Override public void run() { callbacks.bindAppsAdded(addedFinal); //callbacks.addPackage(addedApp); } }); } addedApp=null; // int [] xy = new int[2]; // final int screen = callbacks.findEmptyCell(xy); // Log.d(TAG, "addPackageToSlideData, screen="+screen+",xy="+xy[0]+","+xy[1]); // // if (screen>=0){ // ContentValues values2 = new ContentValues(); // if ((info.activityInfo.name.indexOf(appInfo.packageName)) >= 0) { // intentInfo = "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=" // + appInfo.packageName // + "/" // + info.activityInfo.name // .substring(info.activityInfo.name // .indexOf(appInfo.packageName) // + appInfo.packageName.length()) // + ";end"; // // } else { // intentInfo = "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=" // + appInfo.packageName // + "/" // + info.activityInfo.name + ";end"; // } // values2.put(BaseLauncherColumns.TITLE, // info.loadLabel(packageManager).toString()); // // values2.put(BaseLauncherColumns.INTENT, intentInfo); // values2.put(Favorites.CONTAINER, // Favorites.CONTAINER_DESKTOP); // // // values2.put(Favorites.SCREEN, screen); // values2.put(Favorites.CELLX, xy[0]); // values2.put(Favorites.CELLY, xy[1]); // // values2.put(Favorites.SPANX, 1); // values2.put(Favorites.SPANY, 1); // values2.put(BaseLauncherColumns.ITEM_TYPE, // BaseLauncherColumns.ITEM_TYPE_APPLICATION); // values2.put(BaseLauncherColumns.ICON_TYPE, // BaseLauncherColumns.ICON_TYPE_RESOURCE); // // cr.insert(Favorites.CONTENT_URI_NO_NOTIFICATION, values2); // // values2 = null; // } // // xy = null; // } c.close(); } } return add; } int removePackageFromSlideData(Context context, ContentResolver cr) { Cursor c = cr .query(Applications.CONTENT_URI_NO_NOTIFICATION, new String[] { BaseColumns._ID, BaseLauncherColumns.ITEM_TYPE, Applications.PACKAGENAME, Applications.POSITION }, BaseLauncherColumns.ITEM_TYPE + " in " + "(" + Applications.APPS_TYPE_APP + "," + Applications.APPS_TYPE_FOLDERAPP + ")", null, null); int count = c.getCount(); PackageManager packageManager = context.getPackageManager(); ArrayList<ApplicationInfoEx> mRemoveAppsList = new ArrayList<ApplicationInfoEx>(); if (c != null) { while (c.moveToNext()) { String packageName = c.getString(c .getColumnIndex(Applications.PACKAGENAME)); if (!findPackage(packageName, packageManager)) { ApplicationInfoEx applicationInfoEx = new ApplicationInfoEx(); applicationInfoEx.id = c.getInt(c .getColumnIndex(BaseColumns._ID)); applicationInfoEx.itemType = c .getInt(c .getColumnIndex(BaseLauncherColumns.ITEM_TYPE)); applicationInfoEx.packageName = packageName; applicationInfoEx.position = c.getInt(c .getColumnIndex(Applications.POSITION)); mRemoveAppsList.add(applicationInfoEx); // Log.w(TAG, "removePackageFromSlideData app=" + // packageName+", position="+(applicationInfoEx.position)); } } c.close(); } count -= mRemoveAppsList.size(); for (int i = 0; i < mRemoveAppsList.size(); i++) { ApplicationInfoEx slideInfo = mRemoveAppsList.get(i); c = cr.query(Applications.CONTENT_URI_NO_NOTIFICATION, new String[] { BaseColumns._ID, Applications.PACKAGENAME, Applications.POSITION }, Applications.PACKAGENAME + "=?", new String[] { slideInfo.packageName }, null); if (c != null && c.moveToNext()) { slideInfo.position = c.getInt(c .getColumnIndex(Applications.POSITION)); c.close(); } final Uri updateUri = Applications .getCustomUri("/insertfolder"); if (slideInfo.itemType == Applications.APPS_TYPE_APP) { cr.update(updateUri, null, null, new String[] { String .valueOf(slideInfo.position) }); } Log.w(TAG, "removePackageFromSlideData app=" + slideInfo.packageName + ", position=" + (slideInfo.position)); final Uri deleteUri = Applications.getContentUri( slideInfo.id, true); cr.delete(deleteUri, null, null); } return count; } boolean findPackage(String packageName, PackageManager packageManager) { if (packageName == null || "".equals(packageName)) { return false; } try { packageManager.getPackageInfo(packageName, PackageManager.PERMISSION_GRANTED); return true; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return false; } } public void dumpState() { Log.d(TAG, "mLoader.mLoaderThread.mContext=" + mContext); Log.d(TAG, "mLoader.mLoaderThread.mWaitThread=" + mWaitThread); Log.d(TAG, "mLoader.mLoaderThread.mIsLaunching=" + mIsLaunching); Log.d(TAG, "mLoader.mLoaderThread.mStopped=" + mStopped); Log.d(TAG, "mLoader.mLoaderThread.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); } } public void dumpState() { Log.d(TAG, "mLoader.mItems size=" + mLoader.mItems.size()); if (mLoaderThread != null) { mLoaderThread.dumpState(); } else { Log.d(TAG, "mLoader.mLoaderThread=null"); } } } /** * This is called from the code that adds shortcuts from the intent * receiver. This doesn't have a Cursor, but */ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { return getShortcutInfo(manager, intent, context, null, -1, -1); } /** * Make an ShortcutInfo object for a shortcut that is an application. * * If c is not null, then it will be used to fill in missing data like the * title and icon. */ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, Cursor c, int iconIndex, int titleIndex) { Bitmap icon = null; final ShortcutInfo info = new ShortcutInfo(); ComponentName componentName = intent.getComponent(); if (componentName == null) { return null; } // TODO: See if the PackageManager knows about this case. If it doesn't // then return null & delete this. // the resource -- This may implicitly give us back the fallback icon, // but don't worry about that. All we're doing with usingFallbackIcon is // to avoid saving lots of copies of that in the database, and most apps // have icons anyway. final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0); if (resolveInfo != null) { icon = mIconCache.getIcon(componentName, resolveInfo); } else { // del by liumin, for doov lose wrorkspace icon when in low memory Log.w(TAG, "getShortcutInfo error, resolveInfo null! intent=" + intent); // try { // manager.getPackageInfo(componentName.getPackageName(), 0); // } catch (PackageManager.NameNotFoundException e) { // // As the package not found, do not create it's shortcut // return null; // } } // the db if (icon == null) { if (c != null) { icon = getIconFromCursor(c, iconIndex); } } // the fallback icon if (icon == null) { icon = getFallbackIcon(); info.usingFallbackIcon = true; if (DEBUG_LOADERS) Log.d(TAG, "getShortcutInfo, getFallbackIcon "); } info.setIcon(icon); // from the resource if (resolveInfo != null) { info.title = resolveInfo.activityInfo.loadLabel(manager); } // from the db if (info.title == null) { if (c != null) { info.title = c.getString(titleIndex); } } // fall back to the class name of the activity if (info.title == null) { info.title = componentName.getClassName(); } info.itemType = BaseLauncherColumns.ITEM_TYPE_APPLICATION; return info; } /** * Make an ShortcutInfo object for a shortcut that isn't an application. */ private ShortcutInfo getShortcutInfo(Cursor c, Context context, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, int titleIndex) { Bitmap icon = null; final ShortcutInfo info = new ShortcutInfo(); info.itemType = BaseLauncherColumns.ITEM_TYPE_SHORTCUT; // TODO: If there's an explicit component and we can't install that, // delete it. info.title = c.getString(titleIndex); int iconType = c.getInt(iconTypeIndex); switch (iconType) { case BaseLauncherColumns.ICON_TYPE_RESOURCE: String packageName = c.getString(iconPackageIndex); String resourceName = c.getString(iconResourceIndex); PackageManager packageManager = context.getPackageManager(); info.customIcon = false; // the resource try { Resources resources = packageManager .getResourcesForApplication(packageName); if (resources != null) { final int id = resources.getIdentifier(resourceName, null, null); icon = Utilities.createIconBitmap( resources.getDrawable(id), context); } } catch (Exception e) { // drop this. we have other places to look for icons } // the db if (icon == null) { icon = getIconFromCursor(c, iconIndex); } // the fallback icon if (icon == null) { icon = getFallbackIcon(); info.usingFallbackIcon = true; } break; case BaseLauncherColumns.ICON_TYPE_BITMAP: icon = getIconFromCursor(c, iconIndex); if (icon == null) { icon = getFallbackIcon(); info.customIcon = false; info.usingFallbackIcon = true; } else { info.customIcon = true; } break; default: icon = getFallbackIcon(); info.usingFallbackIcon = true; info.customIcon = false; break; } info.setIcon(icon); return info; } Bitmap getIconFromCursor(Cursor c, int iconIndex) { if (DEBUG_LOADERS) { Log.d(TAG, "getIconFromCursor app=" + c.getString(c .getColumnIndexOrThrow(BaseLauncherColumns.TITLE))); } byte[] data = c.getBlob(iconIndex); try { return BitmapFactory.decodeByteArray(data, 0, data.length); } catch (Exception e) { return null; } } // //check if shortcut is existed // public boolean isDuplicate(Context context, String title, Intent intent) { // return isDuplicate(context, title, intent, false); // } // // public boolean isDuplicate(Context context, String title, Intent intent, boolean strict) { // if (strict) { // return isDuplicateByTitleAndIntent(context, title, intent); // } else { // String pkgName = null; // // try { // pkgName = intent.getComponent().getPackageName(); // } catch (Exception e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // if (pkgName == null) { // return isDuplicateByTitle(context, title); // } else { // return isDuplicateByTitleAndPkgName(context, title, pkgName); // } // // } // } // // private boolean isDuplicateByTitle(Context context, String title){ // //local variable // final ContentResolver cr = context.getContentResolver(); // Cursor c = null; // boolean result = false; // // //body // c = cr.query(Favorites.CONTENT_URI, new String[] { "title" }, // "title=?", new String[] { title }, null); // // try { // result = c.moveToFirst(); //// if (c != null && c.getCount() > 0) { //// result = true; //// } // } finally { // c.close(); // } // // return result; // } // // /** // * @param context // * @param title // * @param data // * @return // */ // private boolean isDuplicateByTitleAndPkgName(Context context, // String title, String pkgName) { // // //local variable // final ContentResolver cr = context.getContentResolver(); // String intentUri = null; // Cursor c = null; // boolean result = false; // // //body // intentUri = "%" + pkgName + "%"; // // c = cr.query(Favorites.CONTENT_URI, new String[] { "title", "intent" }, // "title=? and intent like ?", new String[] { title, intentUri }, // null); // // try { // //result = c.moveToFirst(); // if (c != null && c.getCount() > 0) { // result = true; // } // } finally { // c.close(); // } // // return result; // } // // // // // // /** // * @param context // * @param title // * @param data // * @return // */ // private boolean isDuplicateByTitleAndIntent(Context context, // String title, Intent data) { // final ContentResolver cr = context.getContentResolver(); // String intentUri = new String(""); // Cursor c = null; // boolean result = false; // Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); //// String shortClassName = data.getComponent().getShortClassName(); //// String className = data.getComponent().getClassName(); //// String packageName = data.getComponent().getPackageName(); // // if (intent == null) { // intentUri = data.toUri(0); // } else { // intentUri = intent.toUri(0); // } // // if (intentUri.indexOf("component=") > 0) { // intentUri = "%" // + intentUri.substring(intentUri.indexOf("component=")); // intentUri = intentUri.substring(0, intentUri.indexOf(";")) + "%"; // } else if (intentUri.indexOf("#") > 0) { // intentUri = intentUri.substring(0, intentUri.indexOf("#")) + "%"; // } else { // // don't change it. // } // // c = cr.query(Favorites.CONTENT_URI, new String[] { "title", "intent" }, // "title=? and intent like ?", new String[] { title, intentUri }, // null); // // try { // //result = c.moveToFirst(); // if (c != null && c.getCount() > 0) { // result = true; // } // } finally { // c.close(); // } // // return result; // } public boolean hasShortcut(Context context, ShortcutInfo info, Intent data) { boolean result = false; if (info.itemType==Favorites.ITEM_TYPE_APPLICATION){ final String title = info.title.toString(); final String className = Launcher.getClassName(info, data); final String shortClassName = Launcher.getShortClassName(info, data); final String pkgName = Launcher.getPackageName(info, data); final String intentUri = "%" + className + "%"; final String intentUri2 = "%" + pkgName + "/" + shortClassName + "%"; if (hasShortcutInDB(context, title, intentUri) || hasShortcutInDB(context, title, intentUri2)) return true; } return result; } /** * @param context * @param title * @param intentUri * @return */ private boolean hasShortcutInDB(Context context, final String title, final String intentUri) { boolean result = false; Cursor c = null; try { final ContentResolver cr = context.getContentResolver(); c = cr.query(Favorites.CONTENT_URI, new String[] { "title", "intent" }, "title=? and intent like ?", new String[] { title, intentUri }, null); if (c != null && c.getCount() > 0) { result = true; } } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); result = false; } finally { c.close(); } return result; } ShortcutInfo addShortcut(Context context, Intent data, CellLayout.CellInfo cellInfo, boolean notify) { final ShortcutInfo info = infoFromShortcutIntent(context, data); if (hasShortcut(context, info, data)) return null; addItemToDatabase(context, info, Favorites.CONTAINER_DESKTOP, cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); return info; } // if the count change when update slider data, it will return false; ShortcutInfo addShortcutInDock(Context context, Intent data, ShortcutInfo dockItemInfo, boolean notify) { final ShortcutInfo info = infoFromShortcutIntent(context, data); if (hasShortcut(context, info, data)) return null; addItemToDatabase(context, info, Favorites.CONTAINER_DOCKBAR, dockItemInfo.screen, dockItemInfo.cellX, dockItemInfo.cellY, notify); return info; } private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) { Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); Bitmap icon = null; // boolean filtered = false; boolean customIcon = false; ShortcutIconResource iconResource = null; if (bitmap != null && bitmap instanceof Bitmap) { icon = Utilities.createIconBitmap(new FastBitmapDrawable( (Bitmap) bitmap), context); // filtered = true; customIcon = true; } else { Parcelable extra = data .getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); if (extra != null && extra instanceof ShortcutIconResource) { try { iconResource = (ShortcutIconResource) extra; final PackageManager packageManager = context .getPackageManager(); Resources resources = packageManager .getResourcesForApplication(iconResource.packageName); final int id = resources.getIdentifier( iconResource.resourceName, null, null); icon = Utilities.createIconBitmap( resources.getDrawable(id), context); } catch (Exception e) { Log.w(TAG, "Could not load shortcut icon: " + extra); } } } final ShortcutInfo info = new ShortcutInfo(); if (icon == null) { icon = getFallbackIcon(); info.usingFallbackIcon = true; } info.setIcon(Utilities.createCompoundBitmapEx(name, icon)); info.title = name; info.intent = intent; info.customIcon = customIcon; info.iconResource = iconResource; return info; } private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) { int iconType = c.getInt(iconTypeIndex); switch (iconType) { case BaseLauncherColumns.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 = Utilities.createIconBitmap( resources.getDrawable(id), context); } catch (Exception e) { liveFolderInfo.icon = Utilities.createIconBitmap( context.getResources().getDrawable( R.drawable.ic_launcher_folder), context); } liveFolderInfo.iconResource = new Intent.ShortcutIconResource(); liveFolderInfo.iconResource.packageName = packageName; liveFolderInfo.iconResource.resourceName = resourceName; break; default: liveFolderInfo.icon = Utilities.createIconBitmap(context .getResources().getDrawable(R.drawable.ic_launcher_folder), context); } } void updateSavedIcon(Context context, ShortcutInfo info, Cursor c, int iconIndex) { // If this icon doesn't have a custom icon, check to see // what's stored in the DB, and if it doesn't match what // we're going to show, store what we are going to show back // into the DB. We do this so when we're loading, if the // package manager can't find an icon (for example because // the app is on SD) then we can use that instead. if (info.onExternalStorage && !info.customIcon && !info.usingFallbackIcon) { boolean needSave; byte[] data = c.getBlob(iconIndex); try { if (data != null) { Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); Bitmap loaded = info.getIcon(mIconCache); needSave = !saved.sameAs(loaded); } else { needSave = true; } } catch (Exception e) { needSave = true; } if (needSave) { Log.d(TAG, "going to save icon bitmap for info=" + info); // This is slower than is ideal, but this only happens either // after the froyo OTA or when the app is updated with a new // icon. updateItemInDatabase(context, info); } } } /** * Return an existing UserFolderInfo object if we have encountered this ID * previously, or make a new one. */ 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; } /** * Return an existing UserFolderInfo object if we have encountered this ID * previously, or make a new one. */ 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 (LiveFolderInfo) folderInfo; } @SuppressWarnings("unused") 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(); public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR = new Comparator<ApplicationInfo>() { @Override public final int compare(ApplicationInfo a, ApplicationInfo b) { return sCollator.compare(a.title.toString(), b.title.toString()); } }; public void dumpState() { Log.d(TAG, "mBeforeFirstLoad=" + mBeforeFirstLoad); Log.d(TAG, "mCallbacks=" + mCallbacks); ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); mLoader.dumpState(); } public String dumpState2String(String appName){ StringBuffer str = new StringBuffer(); str.append(ApplicationInfo.dumpApplicationInfoList2String(TAG, "All apps: ", mAllAppsList.data, appName)); // str.append(ApplicationInfo.dumpApplicationInfoList2String(TAG, "Added apps: ", // mAllAppsList.added); // str.append(ApplicationInfo.dumpApplicationInfoList2String(TAG, "Removed apps: ", // mAllAppsList.removed); // str.append(ApplicationInfo.dumpApplicationInfoList2String(TAG, "Modified apps: ", // mAllAppsList.modified); return str.toString(); } /** * @return the mWorkspaceLoaded */ public boolean ismWorkspaceLoaded() { return mWorkspaceLoaded; } /** * @param mWorkspaceLoaded the mWorkspaceLoaded to set */ public void setmWorkspaceLoaded(boolean mWorkspaceLoaded) { this.mWorkspaceLoaded = mWorkspaceLoaded; } }