Merge "Fix long press after already moving off icon" into ub-now-nova
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 013606a..232c6d1 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -887,14 +887,24 @@
}
private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
- ArrayList<ResourceWallpaperInfo> bundledWallpapers =
- new ArrayList<ResourceWallpaperInfo>(24);
+ final PackageManager pm = getPackageManager();
+ final ArrayList<ResourceWallpaperInfo> bundled = new ArrayList<ResourceWallpaperInfo>(24);
+
+ Partner partner = Partner.get(pm);
+ if (partner != null) {
+ final Resources partnerRes = partner.getResources();
+ final int resId = partnerRes.getIdentifier(Partner.RESOURCE_WALLPAPERS, "array",
+ partner.getPackageName());
+ if (resId != 0) {
+ addWallpapers(bundled, partnerRes, partner.getPackageName(), resId);
+ }
+ }
Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
if (r != null) {
try {
Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
- bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
+ addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
} catch (PackageManager.NameNotFoundException e) {
}
}
@@ -903,10 +913,10 @@
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
ResourceWallpaperInfo defaultWallpaperInfo = getPreKKDefaultWallpaperInfo();
if (defaultWallpaperInfo != null) {
- bundledWallpapers.add(0, defaultWallpaperInfo);
+ bundled.add(0, defaultWallpaperInfo);
}
}
- return bundledWallpapers;
+ return bundled;
}
private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
@@ -998,10 +1008,8 @@
}
}
- private ArrayList<ResourceWallpaperInfo> addWallpapers(
- Resources res, String packageName, int listResId) {
- ArrayList<ResourceWallpaperInfo> bundledWallpapers =
- new ArrayList<ResourceWallpaperInfo>(24);
+ private void addWallpapers(ArrayList<ResourceWallpaperInfo> known, Resources res,
+ String packageName, int listResId) {
final String[] extras = res.getStringArray(listResId);
for (String extra : extras) {
int resId = res.getIdentifier(extra, "drawable", packageName);
@@ -1011,14 +1019,13 @@
if (thumbRes != 0) {
ResourceWallpaperInfo wallpaperInfo =
new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
- bundledWallpapers.add(wallpaperInfo);
+ known.add(wallpaperInfo);
// Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
}
} else {
Log.e(TAG, "Couldn't find wallpaper " + extra);
}
}
- return bundledWallpapers;
}
public CropView getCropView() {
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4657422..ad3a1c4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -20,6 +20,10 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- General -->
<skip />
+
+ <!-- URI used to import old favorites. [DO NOT TRANSLATE] -->
+ <string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string>
+
<!-- Application name -->
<string name="application_name">Launcher3</string>
<!-- Accessibility-facing application name -->
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index f85f691..c16e98f 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -26,6 +26,7 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -137,7 +138,8 @@
return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+ " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
- + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+ + ")";
}
public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 42e2ec3..61fa7e5 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -19,6 +19,7 @@
import android.content.ContentValues;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Represents a folder containing shortcuts or apps.
@@ -114,6 +115,6 @@
return "FolderInfo(id=" + this.id + " type=" + this.itemType
+ " container=" + this.container + " screen=" + screenId
+ " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
- + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
}
}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3fe4c60..9ac9edf 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -23,6 +23,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Arrays;
/**
* Represents an item in the launcher.
@@ -182,6 +183,6 @@
public String toString() {
return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
+ " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
- + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
}
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index ba10f51..ad01019 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -30,7 +30,7 @@
private static final String TAG = "LauncherAppState";
private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
- private static final boolean DEBUG = true; // TODO STOPSHIP: set this to false
+ private static final boolean DEBUG = true; // STOPSHIP(cwren) temporary for debugging
private final AppFilter mAppFilter;
private final BuildInfo mBuildInfo;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e5eca62..044ddbb 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -69,12 +69,13 @@
*/
public class LauncherModel extends BroadcastReceiver {
static final boolean DEBUG_LOADERS = false;
+ private static final boolean DEBUG_RECEIVER = true; // STOPSHIP(cwren) temporary for debugging
+
static final String TAG = "Launcher.Model";
// true = use a "More Apps" folder for non-workspace apps on upgrade
// false = strew non-workspace apps across the workspace on upgrade
public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
-
public static final int LOADER_FLAG_NONE = 0;
public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
@@ -193,7 +194,7 @@
mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
ContentProviderClient client = contentResolver.acquireContentProviderClient(
- LauncherSettings.Favorites.OLD_CONTENT_URI);
+ Uri.parse(context.getString(R.string.old_launcher_provider_uri)));
mOldContentProviderExists = (client != null);
if (client != null) {
client.release();
@@ -498,7 +499,9 @@
}
// Clear any deferred bind runnables
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
// Remove any queued bind runnables
mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
// Unbind all the workspace items
@@ -1161,7 +1164,7 @@
*/
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
+ if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
@@ -1315,7 +1318,9 @@
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
@@ -1337,10 +1342,15 @@
void bindRemainingSynchronousPages() {
// Post the remaining side pages to be loaded
if (!mDeferredBindRunnables.isEmpty()) {
- for (final Runnable r : mDeferredBindRunnables) {
+ Runnable[] deferredBindRunnables = null;
+ synchronized (mDeferredBindRunnables) {
+ deferredBindRunnables = mDeferredBindRunnables.toArray(
+ new Runnable[mDeferredBindRunnables.size()]);
+ mDeferredBindRunnables.clear();
+ }
+ for (final Runnable r : deferredBindRunnables) {
mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
}
- mDeferredBindRunnables.clear();
}
}
@@ -2367,7 +2377,9 @@
}
};
if (postOnMainThread) {
- deferredBindRunnables.add(r);
+ synchronized (deferredBindRunnables) {
+ deferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
@@ -2384,7 +2396,9 @@
}
};
if (postOnMainThread) {
- deferredBindRunnables.add(r);
+ synchronized (deferredBindRunnables) {
+ deferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
@@ -2506,7 +2520,9 @@
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
- mDeferredBindRunnables.clear();
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.clear();
+ }
bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
(isLoadingSynchronously ? mDeferredBindRunnables : null));
@@ -2528,7 +2544,9 @@
}
};
if (isLoadingSynchronously) {
- mDeferredBindRunnables.add(r);
+ synchronized (mDeferredBindRunnables) {
+ mDeferredBindRunnables.add(r);
+ }
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index ad43cba..e43b727 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -33,6 +33,7 @@
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -74,7 +75,7 @@
private static final String DATABASE_NAME = "launcher.db";
- private static final int DATABASE_VERSION = 18;
+ private static final int DATABASE_VERSION = 19;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -329,7 +330,7 @@
public void migrateLauncher2Shortcuts() {
mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
- LauncherSettings.Favorites.OLD_CONTENT_URI);
+ Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
}
private static int getDefaultWorkspaceResourceId() {
@@ -371,10 +372,18 @@
private static final String TAG_APPWIDGET = "appwidget";
private static final String TAG_SHORTCUT = "shortcut";
private static final String TAG_FOLDER = "folder";
+ private static final String TAG_PARTNER_FOLDER = "partner-folder";
private static final String TAG_EXTRA = "extra";
private static final String TAG_INCLUDE = "include";
+ private static final String ATTR_TITLE = "title";
+ private static final String ATTR_ICON = "icon";
+ private static final String ATTR_URI = "uri";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_CLASS_NAME = "className";
+
private final Context mContext;
+ private final PackageManager mPackageManager;
private final AppWidgetHost mAppWidgetHost;
private long mMaxItemId = -1;
private long mMaxScreenId = -1;
@@ -384,6 +393,7 @@
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
+ mPackageManager = context.getPackageManager();
mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
// In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
@@ -466,7 +476,7 @@
"/old_favorites?notify=true");
if (!convertDatabase(db, uri, permuteScreensCb, true)) {
// Try and upgrade from the Launcher2 db
- uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+ uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri));
if (!convertDatabase(db, uri, permuteScreensCb, false)) {
// If we fail, then set a flag to load the default workspace
setFlagEmptyDbCreated();
@@ -493,10 +503,34 @@
}
private void removeOrphanedItems(SQLiteDatabase db) {
- db.execSQL("DELETE FROM " + TABLE_FAVORITES + " WHERE " +
+ // Delete items directly on the workspace who's screen id doesn't exist
+ // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
+ // AND container = -100"
+ String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
+ " WHERE " +
LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
- LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS +
- ")");
+ LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
+ " AND " +
+ LauncherSettings.Favorites.CONTAINER + " = " +
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ db.execSQL(removeOrphanedDesktopItems);
+
+ // Delete items contained in folders which no longer exist (after above statement)
+ // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container
+ // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
+ String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
+ " WHERE " +
+ LauncherSettings.Favorites.CONTAINER + " <> " +
+ LauncherSettings.Favorites.CONTAINER_DESKTOP +
+ " AND "
+ + LauncherSettings.Favorites.CONTAINER + " <> " +
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT +
+ " AND "
+ + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
+ LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
+ " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
+ LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
+ db.execSQL(removeOrphanedFolderItems);
}
private void setFlagJustLoadedOldDb() {
@@ -800,14 +834,17 @@
}
if (version < 18) {
+ // No-op
+ version = 18;
+ }
+
+ if (version < 19) {
// Due to a data loss bug, some users may have items associated with screen ids
// which no longer exist. Since this can cause other problems, and since the user
// will never see these items anyway, we use database upgrade as an opportunity to
// clean things up.
-
- // TODO: this needs to be fixed, currently causes data loss.
- //removeOrphanedItems(db);
- version = 18;
+ removeOrphanedItems(db);
+ version = 19;
}
if (version != DATABASE_VERSION) {
@@ -1150,6 +1187,12 @@
}
}
+ private static Intent buildMainIntent() {
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ return intent;
+ }
+
/**
* Loads the default set of favorite packages from an xml file.
*
@@ -1157,13 +1200,10 @@
* @param filterContainerId The specific container id of items to load
*/
private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
- Intent intent = new Intent(Intent.ACTION_MAIN, null);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
ContentValues values = new ContentValues();
if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
- PackageManager packageManager = mContext.getPackageManager();
int i = 0;
try {
XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
@@ -1235,16 +1275,16 @@
}
if (TAG_FAVORITE.equals(name)) {
- long id = addAppShortcut(db, values, a, packageManager, intent);
+ long id = addAppShortcut(db, values, parser);
added = id >= 0;
} else if (TAG_SEARCH.equals(name)) {
added = addSearchWidget(db, values);
} else if (TAG_CLOCK.equals(name)) {
added = addClockWidget(db, values);
} else if (TAG_APPWIDGET.equals(name)) {
- added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
+ added = addAppWidget(parser, attrs, type, db, values, a);
} else if (TAG_SHORTCUT.equals(name)) {
- long id = addUriShortcut(db, values, a);
+ long id = addUriShortcut(db, values, mContext.getResources(), parser);
added = id >= 0;
} else if (TAG_RESOLVE.equals(name)) {
// This looks through the contained favorites (or meta-favorites) and
@@ -1262,8 +1302,7 @@
R.styleable.Favorite);
if (!added) {
if (TAG_FAVORITE.equals(fallback_item_name)) {
- final long id =
- addAppShortcut(db, values, ar, packageManager, intent);
+ final long id = addAppShortcut(db, values, parser);
added = id >= 0;
} else {
Log.e(TAG, "Fallback groups can contain only favorites "
@@ -1273,66 +1312,21 @@
ar.recycle();
}
} else if (TAG_FOLDER.equals(name)) {
- String title;
- int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
- if (titleResId != -1) {
- title = mContext.getResources().getString(titleResId);
- } else {
- title = mContext.getResources().getString(R.string.folder_name);
- }
- values.put(LauncherSettings.Favorites.TITLE, title);
- long folderId = addFolder(db, values);
- added = folderId >= 0;
+ // Folder contents are nested in this XML file
+ added = loadFolder(db, values, mContext.getResources(), parser);
- ArrayList<Long> folderItems = new ArrayList<Long>();
-
- int folderDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > folderDepth) {
- if (type != XmlPullParser.START_TAG) {
- continue;
+ } else if (TAG_PARTNER_FOLDER.equals(name)) {
+ // Folder contents come from an external XML resource
+ final Partner partner = Partner.get(mPackageManager);
+ if (partner != null) {
+ final Resources partnerRes = partner.getResources();
+ final int resId = partnerRes.getIdentifier(Partner.RESOURCE_FOLDER,
+ "xml", partner.getPackageName());
+ if (resId != 0) {
+ final XmlResourceParser partnerParser = partnerRes.getXml(resId);
+ beginDocument(partnerParser, TAG_FOLDER);
+ added = loadFolder(db, values, partnerRes, partnerParser);
}
- final String folder_item_name = parser.getName();
-
- TypedArray ar = mContext.obtainStyledAttributes(attrs,
- R.styleable.Favorite);
- values.clear();
- values.put(LauncherSettings.Favorites.CONTAINER, folderId);
-
- if (LOGD) {
- final String pkg = ar.getString(R.styleable.Favorite_packageName);
- final String uri = ar.getString(R.styleable.Favorite_uri);
- Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
- folder_item_name, uri != null ? uri : pkg));
- }
-
- if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
- long id =
- addAppShortcut(db, values, ar, packageManager, intent);
- if (id >= 0) {
- folderItems.add(id);
- }
- } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
- long id = addUriShortcut(db, values, ar);
- if (id >= 0) {
- folderItems.add(id);
- }
- } else {
- throw new RuntimeException("Folders can " +
- "contain only shortcuts");
- }
- ar.recycle();
- }
- // We can only have folders with >= 2 items, so we need to remove the
- // folder and clean up if less than 2 items were included, or some
- // failed to add, and less than 2 were actually added
- if (folderItems.size() < 2 && folderId >= 0) {
- // We just delete the folder and any items that made it
- deleteId(db, folderId);
- if (folderItems.size() > 0) {
- deleteId(db, folderItems.get(0));
- }
- added = false;
}
}
if (added) i++;
@@ -1354,13 +1348,90 @@
return i;
}
+ /**
+ * Parse folder starting at current {@link XmlPullParser} location.
+ */
+ private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
+ XmlResourceParser parser) throws IOException, XmlPullParserException {
+ final String title;
+ final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
+ if (titleResId != 0) {
+ title = res.getString(titleResId);
+ } else {
+ title = mContext.getResources().getString(R.string.folder_name);
+ }
+
+ values.put(LauncherSettings.Favorites.TITLE, title);
+ long folderId = addFolder(db, values);
+ boolean added = folderId >= 0;
+
+ ArrayList<Long> folderItems = new ArrayList<Long>();
+
+ int type;
+ int folderDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > folderDepth) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String tag = parser.getName();
+
+ final ContentValues childValues = new ContentValues();
+ childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
+
+ if (LOGD) {
+ final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
+ final String uri = getAttributeValue(parser, ATTR_URI);
+ Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
+ tag, uri != null ? uri : pkg));
+ }
+
+ if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
+ final long id = addAppShortcut(db, childValues, parser);
+ if (id >= 0) {
+ folderItems.add(id);
+ }
+ } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
+ final long id = addUriShortcut(db, childValues, res, parser);
+ if (id >= 0) {
+ folderItems.add(id);
+ }
+ } else {
+ throw new RuntimeException("Folders can contain only shortcuts");
+ }
+ }
+
+ // We can only have folders with >= 2 items, so we need to remove the
+ // folder and clean up if less than 2 items were included, or some
+ // failed to add, and less than 2 were actually added
+ if (folderItems.size() < 2 && folderId >= 0) {
+ // Delete the folder
+ deleteId(db, folderId);
+
+ // If we have a single item, promote it to where the folder
+ // would have been.
+ if (folderItems.size() == 1) {
+ final ContentValues childValues = new ContentValues();
+ copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
+ copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
+ copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
+ copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
+
+ final long id = folderItems.get(0);
+ db.update(TABLE_FAVORITES, childValues,
+ LauncherSettings.Favorites._ID + "=" + id, null);
+ } else {
+ added = false;
+ }
+ }
+ return added;
+ }
+
// A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
// logical choice for what shortcut should be used for that intent exists, then it is
// added. Otherwise add nothing.
- private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values, TypedArray a,
- PackageManager packageManager, Intent intent) {
- final String intentUri = a.getString(R.styleable.Favorite_uri);
-
+ private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
+ String intentUri) {
Intent metaIntent;
try {
metaIntent = Intent.parseUri(intentUri, 0);
@@ -1369,16 +1440,16 @@
return -1;
}
- ResolveInfo resolved = packageManager.resolveActivity(metaIntent,
+ ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
PackageManager.MATCH_DEFAULT_ONLY);
- final List<ResolveInfo> appList = packageManager.queryIntentActivities(
+ final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
// Verify that the result is an app and not just the resolver dialog asking which
// app to use.
if (wouldLaunchResolverActivity(resolved, appList)) {
// If only one of the results is a system app then choose that as the default.
- final ResolveInfo systemApp = getSingleSystemActivity(appList, packageManager);
+ final ResolveInfo systemApp = getSingleSystemActivity(appList);
if (systemApp == null) {
// There is no logical choice for this meta-favorite, so rather than making
// a bad choice just add nothing.
@@ -1389,20 +1460,20 @@
resolved = systemApp;
}
final ActivityInfo info = resolved.activityInfo;
+ final Intent intent = buildMainIntent();
intent.setComponent(new ComponentName(info.packageName, info.name));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- return addAppShortcut(db, values, info.loadLabel(packageManager).toString(), intent);
+ return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
}
- private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList,
- PackageManager packageManager) {
+ private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
ResolveInfo systemResolve = null;
final int N = appList.size();
for (int i = 0; i < N; ++i) {
try {
- ApplicationInfo info = packageManager.getApplicationInfo(
+ ApplicationInfo info = mPackageManager.getApplicationInfo(
appList.get(i).activityInfo.packageName, 0);
if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (systemResolve != null) {
@@ -1433,38 +1504,40 @@
return true;
}
- private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
- PackageManager packageManager, Intent intent) {
- if (a.hasValue(R.styleable.Favorite_packageName)
- && a.hasValue(R.styleable.Favorite_className)) {
+ private long addAppShortcut(SQLiteDatabase db, ContentValues values,
+ XmlResourceParser parser) {
+ final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
+ final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+ final String uri = getAttributeValue(parser, ATTR_URI);
+
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
ActivityInfo info;
- String packageName = a.getString(R.styleable.Favorite_packageName);
- String className = a.getString(R.styleable.Favorite_className);
try {
ComponentName cn;
try {
cn = new ComponentName(packageName, className);
- info = packageManager.getActivityInfo(cn, 0);
+ info = mPackageManager.getActivityInfo(cn, 0);
} catch (PackageManager.NameNotFoundException nnfe) {
- String[] packages = packageManager.currentToCanonicalPackageNames(
+ String[] packages = mPackageManager.currentToCanonicalPackageNames(
new String[] { packageName });
cn = new ComponentName(packages[0], className);
- info = packageManager.getActivityInfo(cn, 0);
+ info = mPackageManager.getActivityInfo(cn, 0);
}
+ final Intent intent = buildMainIntent();
intent.setComponent(cn);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- return addAppShortcut(db, values, info.loadLabel(packageManager).toString(),
+ return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
intent);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to add favorite: " + packageName +
"/" + className, e);
}
return -1;
- } else if (a.hasValue(R.styleable.Favorite_uri)) {
+ } else if (!TextUtils.isEmpty(uri)) {
// If no component specified try to find a shortcut to add from the URI.
- return addAppShortcutByUri(db, values, a, packageManager, intent);
+ return addAppShortcutByUri(db, values, uri);
} else {
Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
return -1;
@@ -1538,8 +1611,8 @@
}
private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
- SQLiteDatabase db, ContentValues values, TypedArray a,
- PackageManager packageManager) throws XmlPullParserException, IOException {
+ SQLiteDatabase db, ContentValues values, TypedArray a)
+ throws XmlPullParserException, IOException {
String packageName = a.getString(R.styleable.Favorite_packageName);
String className = a.getString(R.styleable.Favorite_className);
@@ -1551,13 +1624,13 @@
boolean hasPackage = true;
ComponentName cn = new ComponentName(packageName, className);
try {
- packageManager.getReceiverInfo(cn, 0);
+ mPackageManager.getReceiverInfo(cn, 0);
} catch (Exception e) {
- String[] packages = packageManager.currentToCanonicalPackageNames(
+ String[] packages = mPackageManager.currentToCanonicalPackageNames(
new String[] { packageName });
cn = new ComponentName(packages[0], className);
try {
- packageManager.getReceiverInfo(cn, 0);
+ mPackageManager.getReceiverInfo(cn, 0);
} catch (Exception e1) {
hasPackage = false;
}
@@ -1632,17 +1705,15 @@
return allocatedAppWidgets;
}
- private long addUriShortcut(SQLiteDatabase db, ContentValues values,
- TypedArray a) {
- Resources r = mContext.getResources();
-
- final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
- final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
+ private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
+ XmlResourceParser parser) {
+ final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
+ final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
Intent intent;
String uri = null;
try {
- uri = a.getString(R.styleable.Favorite_uri);
+ uri = getAttributeValue(parser, ATTR_URI);
intent = Intent.parseUri(uri, 0);
} catch (URISyntaxException e) {
Log.w(TAG, "Shortcut has malformed uri: " + uri);
@@ -1657,13 +1728,13 @@
long id = generateNewItemId();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
values.put(Favorites.INTENT, intent.toUri(0));
- values.put(Favorites.TITLE, r.getString(titleResId));
+ values.put(Favorites.TITLE, res.getString(titleResId));
values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
values.put(Favorites.SPANX, 1);
values.put(Favorites.SPANY, 1);
values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
- values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
- values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
+ values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
+ values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
values.put(Favorites._ID, id);
if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
@@ -1955,6 +2026,38 @@
return selectWhere.toString();
}
+ /**
+ * Return attribute value, attempting launcher-specific namespace first
+ * before falling back to anonymous attribute.
+ */
+ static String getAttributeValue(XmlResourceParser parser, String attribute) {
+ String value = parser.getAttributeValue(
+ "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
+ if (value == null) {
+ value = parser.getAttributeValue(null, attribute);
+ }
+ return value;
+ }
+
+ /**
+ * Return attribute resource value, attempting launcher-specific namespace
+ * first before falling back to anonymous attribute.
+ */
+ static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
+ int defaultValue) {
+ int value = parser.getAttributeResourceValue(
+ "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
+ defaultValue);
+ if (value == defaultValue) {
+ value = parser.getAttributeResourceValue(null, attribute, defaultValue);
+ }
+ return value;
+ }
+
+ private static void copyInteger(ContentValues from, ContentValues to, String key) {
+ to.put(key, from.getAsInteger(key));
+ }
+
static class SqlArguments {
public final String table;
public final String where;
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
new file mode 100644
index 0000000..866b17c
--- /dev/null
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 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.launcher3;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An executor service that executes its tasks on the main thread.
+ *
+ * Shutting down this executor is not supported.
+ */
+public class MainThreadExecutor extends AbstractExecutorService {
+
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ /**
+ * Not supported and throws an exception when used.
+ */
+ @Override
+ @Deprecated
+ public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java
new file mode 100644
index 0000000..79c763d
--- /dev/null
+++ b/src/com/android/launcher3/Partner.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 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.launcher3;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.util.Log;
+
+/**
+ * Utilities to discover and interact with partner customizations. There can
+ * only be one set of customizations on a device, and it must be bundled with
+ * the system.
+ */
+public class Partner {
+ private static final String TAG = "Partner";
+
+ /** Marker action used to discover partner */
+ private static final String
+ ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION";
+
+ public static final String RESOURCE_FOLDER = "partner_folder";
+ public static final String RESOURCE_WALLPAPERS = "partner_wallpapers";
+
+ private static boolean sSearched = false;
+ private static Partner sPartner;
+
+ /**
+ * Find and return partner details, or {@code null} if none exists.
+ */
+ public static synchronized Partner get(PackageManager pm) {
+ if (!sSearched) {
+ final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (info.activityInfo != null &&
+ (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ final String packageName = info.activityInfo.packageName;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ sPartner = new Partner(packageName, res);
+ break;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
+ }
+ }
+ }
+ sSearched = true;
+ }
+ return sPartner;
+ }
+
+ private final String mPackageName;
+ private final Resources mResources;
+
+ private Partner(String packageName, Resources res) {
+ mPackageName = packageName;
+ mResources = res;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public Resources getResources() {
+ return mResources;
+ }
+}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 5afa784..92b582d 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -27,6 +27,7 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Represents a launchable icon on the workspaces and in folders.
@@ -226,7 +227,7 @@
return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id
+ " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
+ " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
- + " dropPos=" + dropPos + ")";
+ + " dropPos=" + Arrays.toString(dropPos) + ")";
}
public static void dumpShortcutInfoList(String tag, String label,
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 36152f8..5dad2a8 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -35,6 +35,8 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
abstract class SoftReferenceThreadLocal<T> {
private ThreadLocal<SoftReference<T>> mThreadLocal;
@@ -137,6 +139,8 @@
private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
private final static HashSet<String> sInvalidPackages;
+ private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+
static {
sInvalidPackages = new HashSet<String>();
}
@@ -492,7 +496,8 @@
Drawable drawable = null;
if (previewImage != 0) {
- drawable = mPackageManager.getDrawable(packageName, previewImage, null);
+ drawable = mutateOnMainThread(
+ mPackageManager.getDrawable(packageName, previewImage, null));
if (drawable == null) {
Log.w(TAG, "Can't load widget preview drawable 0x" +
Integer.toHexString(previewImage) + " for provider: " + provider);
@@ -511,6 +516,7 @@
if (cellHSpan < 1) cellHSpan = 1;
if (cellVSpan < 1) cellVSpan = 1;
+ // This Drawable is not directly drawn, so there's no need to mutate it.
BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
.getDrawable(R.drawable.widget_tile);
final int previewDrawableWidth = previewDrawable
@@ -548,7 +554,7 @@
int yoffset =
(int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
if (iconId > 0)
- icon = mIconCache.getFullResIcon(packageName, iconId);
+ icon = mutateOnMainThread(mIconCache.getFullResIcon(packageName, iconId));
if (icon != null) {
renderDrawableToBitmap(icon, defaultPreview, hoffset,
yoffset, (int) (mAppIconSize * iconScale),
@@ -617,7 +623,7 @@
c.setBitmap(null);
}
// Render the icon
- Drawable icon = mIconCache.getFullResIcon(info);
+ Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info));
int paddingTop = mContext.
getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
@@ -677,4 +683,19 @@
}
}
+ private Drawable mutateOnMainThread(final Drawable drawable) {
+ try {
+ return mMainThreadExecutor.submit(new Callable<Drawable>() {
+ @Override
+ public Drawable call() throws Exception {
+ return drawable.mutate();
+ }
+ }).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index c984ad6..9800cf3 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1992,6 +1992,15 @@
mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
}
+ private Rect getDrawableBounds(Drawable d) {
+ Rect bounds = new Rect();
+ d.copyBounds(bounds);
+ if (bounds.width() == 0 || bounds.height() == 0) {
+ bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ }
+ return bounds;
+ }
+
public void onExternalDragStartedWithItem(View v) {
final Canvas canvas = new Canvas();
@@ -2006,8 +2015,9 @@
if (v instanceof TextView) {
TextView tv = (TextView) v;
Drawable d = tv.getCompoundDrawables()[1];
- bmpWidth = d.getIntrinsicWidth();
- bmpHeight = d.getIntrinsicHeight();
+ Rect bounds = getDrawableBounds(d);
+ bmpWidth = bounds.width();
+ bmpHeight = bounds.height();
}
// Compose the bitmap to create the icon from
@@ -2527,7 +2537,8 @@
destCanvas.save();
if (v instanceof TextView && pruneToDrawable) {
Drawable d = ((TextView) v).getCompoundDrawables()[1];
- clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
+ Rect bounds = getDrawableBounds(d);
+ clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
destCanvas.translate(padding / 2, padding / 2);
d.draw(destCanvas);
} else {
@@ -2568,8 +2579,9 @@
if (v instanceof TextView) {
Drawable d = ((TextView) v).getCompoundDrawables()[1];
- b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
- d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
+ Rect bounds = getDrawableBounds(d);
+ b = Bitmap.createBitmap(bounds.width() + padding,
+ bounds.height() + padding, Bitmap.Config.ARGB_8888);
} else {
b = Bitmap.createBitmap(
v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);