diff options
Diffstat (limited to 'src/com/android/launcher3/LauncherProvider.java')
-rw-r--r-- | src/com/android/launcher3/LauncherProvider.java | 886 |
1 files changed, 695 insertions, 191 deletions
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index a080dd8ca..6cc1688de 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -32,9 +32,10 @@ import android.content.Intent; import android.content.OperationApplicationException; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.Cursor; import android.database.SQLException; @@ -48,12 +49,13 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; -import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; -import android.util.Xml; +import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.ProviderConfig; import org.xmlpull.v1.XmlPullParser; @@ -63,6 +65,7 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -72,7 +75,7 @@ public class LauncherProvider extends ContentProvider { private static final String DATABASE_NAME = "launcher.db"; - private static final int DATABASE_VERSION = 17; + private static final int DATABASE_VERSION = 20; static final String OLD_AUTHORITY = "com.android.launcher2.settings"; static final String AUTHORITY = ProviderConfig.AUTHORITY; @@ -87,12 +90,14 @@ public class LauncherProvider extends ContentProvider { "UPGRADED_FROM_OLD_DATABASE"; static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; - static final String DEFAULT_WORKSPACE_RESOURCE_ID = - "DEFAULT_WORKSPACE_RESOURCE_ID"; private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; + private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd"; + + private LauncherProviderChangeListener mListener; + /** * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when * {@link AppWidgetHost#deleteHost()} is called during database creation. @@ -116,6 +121,10 @@ public class LauncherProvider extends ContentProvider { return mOpenHelper.wasNewDbCreated(); } + public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { + mListener = listener; + } + @Override public String getType(Uri uri) { SqlArguments args = new SqlArguments(uri, null, null); @@ -146,7 +155,7 @@ public class LauncherProvider extends ContentProvider { if (values == null) { throw new RuntimeException("Error: attempting to insert null values"); } - if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) { + if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) { throw new RuntimeException("Error: attempting to add item without specifying an id"); } helper.checkId(table, values); @@ -163,6 +172,14 @@ public class LauncherProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues initialValues) { SqlArguments args = new SqlArguments(uri); + // In very limited cases, we support system|signature permission apps to add to the db + String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD); + if (externalAdd != null && "true".equals(externalAdd)) { + if (!mOpenHelper.initializeExternalAdd(initialValues)) { + return null; + } + } + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); addModifiedTime(initialValues); final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); @@ -174,6 +191,7 @@ public class LauncherProvider extends ContentProvider { return uri; } + @Override public int bulkInsert(Uri uri, ContentValues[] values) { SqlArguments args = new SqlArguments(uri); @@ -242,6 +260,9 @@ public class LauncherProvider extends ContentProvider { // always notify the backup agent LauncherBackupAgentHelper.dataChanged(getContext()); + if (mListener != null) { + mListener.onLauncherProviderChange(); + } } private void addModifiedTime(ContentValues values) { @@ -287,45 +308,64 @@ public class LauncherProvider extends ContentProvider { } /** - * @param workspaceResId that can be 0 to use default or non-zero for specific resource + * Clears all the data for a fresh start. */ - synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) { + synchronized public void createEmptyDB() { + mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); + } + + /** + * Loads the default workspace based on the following priority scheme: + * 1) From a package provided by play store + * 2) From a partner configuration APK, already in the system image + * 3) The default configuration for the particular device + */ + synchronized public void loadDefaultFavoritesIfNecessary() { String spKey = LauncherAppState.getSharedPreferencesKey(); SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { Log.d(TAG, "loading default workspace"); - int workspaceResId = origWorkspaceResId; - // Use default workspace resource if none provided - if (workspaceResId == 0) { - workspaceResId = - sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, getDefaultWorkspaceResourceId()); + WorkspaceLoader loader = AutoInstallsLayout.get(getContext(), + mOpenHelper.mAppWidgetHost, mOpenHelper); + + if (loader == null) { + final Partner partner = Partner.get(getContext().getPackageManager()); + if (partner != null && partner.hasDefaultLayout()) { + final Resources partnerRes = partner.getResources(); + int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, + "xml", partner.getPackageName()); + if (workspaceResId != 0) { + loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId); + } + } } - // Populate favorites table with initial favorites - SharedPreferences.Editor editor = sp.edit(); - editor.remove(EMPTY_DATABASE_CREATED); - if (origWorkspaceResId != 0) { - editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId); + if (loader == null) { + loader = new SimpleWorkspaceLoader(mOpenHelper, getContext().getResources(), + getDefaultWorkspaceResourceId()); } - mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId); - mOpenHelper.setFlagJustLoadedOldDb(); + // Populate favorites table with initial favorites + SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED); + mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader); editor.commit(); } } 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() { + LauncherAppState app = LauncherAppState.getInstance(); + DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); if (LauncherAppState.isDisableAllApps()) { - return R.xml.default_workspace_no_all_apps; + return grid.defaultNoAllAppsLayoutId; } else { - return R.xml.default_workspace; + return grid.defaultLayoutId; } } @@ -351,18 +391,39 @@ public class LauncherProvider extends ContentProvider { mOpenHelper = new DatabaseHelper(getContext()); } - private static class DatabaseHelper extends SQLiteOpenHelper { + private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { + private static final String TAG_RESOLVE = "resolve"; private static final String TAG_FAVORITES = "favorites"; private static final String TAG_FAVORITE = "favorite"; - private static final String TAG_CLOCK = "clock"; - private static final String TAG_SEARCH = "search"; 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"; + // Style attrs -- "Favorite" + private static final String ATTR_CLASS_NAME = "className"; + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_CONTAINER = "container"; + private static final String ATTR_SCREEN = "screen"; + private static final String ATTR_X = "x"; + private static final String ATTR_Y = "y"; + private static final String ATTR_SPAN_X = "spanX"; + private static final String ATTR_SPAN_Y = "spanY"; + private static final String ATTR_ICON = "icon"; + private static final String ATTR_TITLE = "title"; + private static final String ATTR_URI = "uri"; + + // Style attrs -- "Include" + private static final String ATTR_WORKSPACE = "workspace"; + + // Style attrs -- "Extra" + private static final String ATTR_KEY = "key"; + private static final String ATTR_VALUE = "value"; + private final Context mContext; + private final PackageManager mPackageManager; private final AppWidgetHost mAppWidgetHost; private long mMaxItemId = -1; private long mMaxScreenId = -1; @@ -372,6 +433,7 @@ public class LauncherProvider extends ContentProvider { 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 @@ -407,6 +469,10 @@ public class LauncherProvider extends ContentProvider { mMaxScreenId = 0; mNewDbCreated = true; + UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); + long userSerialNumber = userManager.getSerialNumberForUser( + UserHandleCompat.myUserHandle()); + db.execSQL("CREATE TABLE favorites (" + "_id INTEGER PRIMARY KEY," + "title TEXT," + @@ -428,7 +494,8 @@ public class LauncherProvider extends ContentProvider { "displayMode INTEGER," + "appWidgetProvider TEXT," + "modified INTEGER NOT NULL DEFAULT 0," + - "restored INTEGER NOT NULL DEFAULT 0" + + "restored INTEGER NOT NULL DEFAULT 0," + + "profileId INTEGER DEFAULT " + userSerialNumber + ");"); addWorkspacesTable(db); @@ -454,7 +521,7 @@ public class LauncherProvider extends ContentProvider { "/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(); @@ -480,6 +547,37 @@ public class LauncherProvider extends ContentProvider { ");"); } + private void removeOrphanedItems(SQLiteDatabase db) { + // 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 + ")" + + " 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() { String spKey = LauncherAppState.getSharedPreferencesKey(); SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); @@ -691,7 +789,8 @@ public class LauncherProvider extends ContentProvider { } // Add default hotseat icons - loadFavorites(db, R.xml.update_workspace); + loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(), + R.xml.update_workspace)); version = 9; } @@ -780,6 +879,28 @@ public class LauncherProvider extends ContentProvider { version = 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. + removeOrphanedItems(db); + version = 19; + } + + if (version < 20) { + // Add userId column + if (addProfileColumn(db)) { + version = 20; + } + // else old version remains, which means we wipe old data + } + if (version != DATABASE_VERSION) { Log.w(TAG, "Destroying all old data."); db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); @@ -789,6 +910,47 @@ public class LauncherProvider extends ContentProvider { } } + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // This shouldn't happen -- throw our hands up in the air and start over. + Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + + ". Wiping databse."); + createEmptyDB(db); + } + + + /** + * Clears all the data for a fresh start. + */ + public void createEmptyDB(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS); + onCreate(db); + } + + private boolean addProfileColumn(SQLiteDatabase db) { + db.beginTransaction(); + try { + UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); + // Default to the serial number of this user, for older + // shortcuts. + long userSerialNumber = userManager.getSerialNumberForUser( + UserHandleCompat.myUserHandle()); + // Insert new column for holding user serial number + db.execSQL("ALTER TABLE favorites " + + "ADD COLUMN profileId INTEGER DEFAULT " + + userSerialNumber + ";"); + db.setTransactionSuccessful(); + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + return false; + } finally { + db.endTransaction(); + } + return true; + } + private boolean updateContactsShortcuts(SQLiteDatabase db) { final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, new int[] { Favorites.ITEM_TYPE_SHORTCUT }); @@ -930,6 +1092,7 @@ public class LauncherProvider extends ContentProvider { // constructor from the worker thread; however, this doesn't extend until after the // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp // after that point + @Override public long generateNewItemId() { if (mMaxItemId < 0) { throw new RuntimeException("Error: max item id was not initialized"); @@ -938,6 +1101,11 @@ public class LauncherProvider extends ContentProvider { return mMaxItemId; } + @Override + public long insertAndCheck(SQLiteDatabase db, ContentValues values) { + return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); + } + public void updateMaxItemId(long id) { mMaxItemId = id + 1; } @@ -1102,6 +1270,93 @@ public class LauncherProvider extends ContentProvider { if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId); } + private boolean initializeExternalAdd(ContentValues values) { + // 1. Ensure that externally added items have a valid item id + long id = generateNewItemId(); + values.put(LauncherSettings.Favorites._ID, id); + + // 2. In the case of an app widget, and if no app widget id is specified, we + // attempt allocate and bind the widget. + Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); + if (itemType != null && + itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && + !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) { + + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + ComponentName cn = ComponentName.unflattenFromString( + values.getAsString(Favorites.APPWIDGET_PROVIDER)); + + if (cn != null) { + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); + if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) { + return false; + } + } catch (RuntimeException e) { + Log.e(TAG, "Failed to initialize external widget", e); + return false; + } + } else { + return false; + } + } + + // Add screen id if not present + long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); + if (!addScreenIdIfNecessary(screenId)) { + return false; + } + return true; + } + + // Returns true of screen id exists, or if successfully added + private boolean addScreenIdIfNecessary(long screenId) { + if (!hasScreenId(screenId)) { + int rank = getMaxScreenRank() + 1; + + ContentValues v = new ContentValues(); + v.put(LauncherSettings.WorkspaceScreens._ID, screenId); + v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); + if (dbInsertAndCheck(this, getWritableDatabase(), + TABLE_WORKSPACE_SCREENS, null, v) < 0) { + return false; + } + } + return true; + } + + private boolean hasScreenId(long screenId) { + SQLiteDatabase db = getWritableDatabase(); + Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE " + + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null); + if (c != null) { + int count = c.getCount(); + c.close(); + return count > 0; + } else { + return false; + } + } + + private int getMaxScreenRank() { + SQLiteDatabase db = getWritableDatabase(); + Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK + + ") FROM " + TABLE_WORKSPACE_SCREENS, null); + + // get the result + final int maxRankIndex = 0; + int rank = -1; + if (c != null && c.moveToNext()) { + rank = c.getInt(maxRankIndex); + } + if (c != null) { + c.close(); + } + + return rank; + } + private static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException { int type; @@ -1120,24 +1375,55 @@ public class LauncherProvider extends ContentProvider { } } + private static Intent buildMainIntent() { + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + return intent; + } + + private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) { + ArrayList<Long> screenIds = new ArrayList<Long>(); + // TODO: Use multiple loaders with fall-back and transaction. + int count = loader.loadLayout(db, screenIds); + + // Add the screens specified by the items above + Collections.sort(screenIds); + int rank = 0; + ContentValues values = new ContentValues(); + for (Long id : screenIds) { + values.clear(); + values.put(LauncherSettings.WorkspaceScreens._ID, id); + values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); + if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) { + throw new RuntimeException("Failed initialize screen table" + + "from default layout"); + } + rank++; + } + + // Ensure that the max ids are initialized + mMaxItemId = initializeMaxItemId(db); + mMaxScreenId = initializeMaxScreenId(db); + + return count; + } + /** * Loads the default set of favorite packages from an xml file. * * @param db The database to write the values into * @param filterContainerId The specific container id of items to load + * @param the set of screenIds which are used by the favorites */ - private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { - Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - ContentValues values = new ContentValues(); + private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId, + ArrayList<Long> screenIds) { + 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; + int count = 0; try { - XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); - AttributeSet attrs = Xml.asAttributeSet(parser); + XmlResourceParser parser = res.getXml(workspaceResourceId); beginDocument(parser, TAG_FAVORITES); final int depth = parser.getDepth(); @@ -1154,38 +1440,34 @@ public class LauncherProvider extends ContentProvider { final String name = parser.getName(); if (TAG_INCLUDE.equals(name)) { - final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include); - final int resId = a.getResourceId(R.styleable.Include_workspace, 0); + final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0); if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"), "", resId)); if (resId != 0 && resId != workspaceResourceId) { // recursively load some more favorites, why not? - i += loadFavorites(db, resId); + count += loadFavoritesRecursive(db, res, resId, screenIds); added = false; } else { Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId)); } - a.recycle(); - if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), "")); continue; } // Assuming it's a <favorite> at this point - TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite); - long container = LauncherSettings.Favorites.CONTAINER_DESKTOP; - if (a.hasValue(R.styleable.Favorite_container)) { - container = Long.valueOf(a.getString(R.styleable.Favorite_container)); + String strContainer = getAttributeValue(parser, ATTR_CONTAINER); + if (strContainer != null) { + container = Long.valueOf(strContainer); } - String screen = a.getString(R.styleable.Favorite_screen); - String x = a.getString(R.styleable.Favorite_x); - String y = a.getString(R.styleable.Favorite_y); + String screen = getAttributeValue(parser, ATTR_SCREEN); + String x = getAttributeValue(parser, ATTR_X); + String y = getAttributeValue(parser, ATTR_Y); values.clear(); values.put(LauncherSettings.Favorites.CONTAINER, container); @@ -1194,8 +1476,8 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.Favorites.CELLY, y); if (LOGD) { - final String title = a.getString(R.styleable.Favorite_title); - final String pkg = a.getString(R.styleable.Favorite_packageName); + final String title = getAttributeValue(parser, ATTR_TITLE); + final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String something = title != null ? title : pkg; Log.v(TAG, String.format( ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"), @@ -1205,82 +1487,62 @@ public class LauncherProvider extends ContentProvider { } 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, type, db, values); } else if (TAG_SHORTCUT.equals(name)) { - long id = addUriShortcut(db, values, a); + long id = addUriShortcut(db, values, res, parser); added = id >= 0; - } 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; - - ArrayList<Long> folderItems = new ArrayList<Long>(); - - int folderDepth = parser.getDepth(); + } else if (TAG_RESOLVE.equals(name)) { + // This looks through the contained favorites (or meta-favorites) and + // attempts to add them as shortcuts in the fallback group's location + // until one is added successfully. + added = false; + final int groupDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > folderDepth) { + parser.getDepth() > groupDepth) { if (type != XmlPullParser.START_TAG) { continue; } - 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); + final String fallback_item_name = parser.getName(); + if (!added) { + if (TAG_FAVORITE.equals(fallback_item_name)) { + final long id = addAppShortcut(db, values, parser); + added = id >= 0; + } else { + Log.e(TAG, "Fallback groups can contain only favorites, found " + + fallback_item_name); } - } 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)); + } else if (TAG_FOLDER.equals(name)) { + // Folder contents are nested in this XML file + added = loadFolder(db, values, res, parser); + + } 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.RES_FOLDER, + "xml", partner.getPackageName()); + if (resId != 0) { + final XmlResourceParser partnerParser = partnerRes.getXml(resId); + beginDocument(partnerParser, TAG_FOLDER); + added = loadFolder(db, values, partnerRes, partnerParser); } - added = false; } } - if (added) i++; - a.recycle(); + if (added) { + long screenId = Long.parseLong(screen); + // Keep track of the set of screens which need to be added to the db. + if (!screenIds.contains(screenId) && + container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + screenIds.add(screenId); + } + count++; + } } } catch (XmlPullParserException e) { Log.w(TAG, "Got exception parsing favorites.", e); @@ -1289,50 +1551,231 @@ public class LauncherProvider extends ContentProvider { } catch (RuntimeException e) { Log.w(TAG, "Got exception parsing favorites.", e); } + return count; + } - // Update the max item id after we have loaded the database - if (mMaxItemId == -1) { - mMaxItemId = initializeMaxItemId(db); + /** + * Parse folder items starting at {@link XmlPullParser} location. Allow recursive + * includes of items. + */ + private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser, + ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException { + 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 if (TAG_INCLUDE.equals(tag) && folderId >= 0) { + addToFolder(db, res, parser, folderItems, folderId); + } else { + throw new RuntimeException("Folders can contain only shortcuts"); + } } + } - 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>(); + addToFolder(db, res, parser, folderItems, folderId); + + // 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; } - private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, - PackageManager packageManager, Intent intent) { - long id = -1; - ActivityInfo info; - String packageName = a.getString(R.styleable.Favorite_packageName); - String className = a.getString(R.styleable.Favorite_className); + // 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, + String intentUri) { + Intent metaIntent; try { - ComponentName cn; + metaIntent = Intent.parseUri(intentUri, 0); + } catch (URISyntaxException e) { + Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e); + return -1; + } + + ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent, + PackageManager.MATCH_DEFAULT_ONLY); + 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); + if (systemApp == null) { + // There is no logical choice for this meta-favorite, so rather than making + // a bad choice just add nothing. + Log.w(TAG, "No preference or single system activity found for " + + metaIntent.toString()); + return -1; + } + resolved = systemApp; + } + final ActivityInfo info = resolved.activityInfo; + final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName); + if (intent == null) { + return -1; + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent); + } + + private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) { + ResolveInfo systemResolve = null; + final int N = appList.size(); + for (int i = 0; i < N; ++i) { try { - cn = new ComponentName(packageName, className); - info = packageManager.getActivityInfo(cn, 0); - } catch (PackageManager.NameNotFoundException nnfe) { - String[] packages = packageManager.currentToCanonicalPackageNames( - new String[] { packageName }); - cn = new ComponentName(packages[0], className); - info = packageManager.getActivityInfo(cn, 0); + ApplicationInfo info = mPackageManager.getApplicationInfo( + appList.get(i).activityInfo.packageName, 0); + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (systemResolve != null) { + return null; + } else { + systemResolve = appList.get(i); + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to get info about resolve results", e); + return null; } - id = generateNewItemId(); - intent.setComponent(cn); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - values.put(Favorites.INTENT, intent.toUri(0)); - values.put(Favorites.TITLE, info.loadLabel(packageManager).toString()); - values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); - values.put(Favorites.SPANX, 1); - values.put(Favorites.SPANY, 1); - values.put(Favorites._ID, generateNewItemId()); - if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { - return -1; + } + return systemResolve; + } + + private boolean wouldLaunchResolverActivity(ResolveInfo resolved, + List<ResolveInfo> appList) { + // If the list contains the above resolved activity, then it can't be + // ResolverActivity itself. + for (int i = 0; i < appList.size(); ++i) { + ResolveInfo tmp = appList.get(i); + if (tmp.activityInfo.name.equals(resolved.activityInfo.name) + && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { + return false; } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to add favorite: " + packageName + - "/" + className, e); } - return id; + return true; + } + + 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; + try { + ComponentName cn; + try { + cn = new ComponentName(packageName, className); + info = mPackageManager.getActivityInfo(cn, 0); + } catch (PackageManager.NameNotFoundException nnfe) { + String[] packages = mPackageManager.currentToCanonicalPackageNames( + new String[] { packageName }); + cn = new ComponentName(packages[0], className); + 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(mPackageManager).toString(), + intent); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to add favorite: " + packageName + + "/" + className, e); + } + return -1; + } else if (!TextUtils.isEmpty(uri)) { + // If no component specified try to find a shortcut to add from the URI. + return addAppShortcutByUri(db, values, uri); + } else { + Log.e(TAG, "Skipping invalid <favorite> with no component or uri"); + return -1; + } + } + + private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title, + Intent intent) { + long id = generateNewItemId(); + values.put(Favorites.INTENT, intent.toUri(0)); + values.put(Favorites.TITLE, title); + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION); + values.put(Favorites.SPANX, 1); + values.put(Favorites.SPANY, 1); + values.put(Favorites._ID, id); + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { + return -1; + } else { + return id; + } } private long addFolder(SQLiteDatabase db, ContentValues values) { @@ -1374,23 +1817,12 @@ public class LauncherProvider extends ContentProvider { return null; } - private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) { - ComponentName cn = getSearchWidgetProvider(); - return addAppWidget(db, values, cn, 4, 1, null); - } - - private boolean addClockWidget(SQLiteDatabase db, ContentValues values) { - ComponentName cn = new ComponentName("com.android.alarmclock", - "com.android.alarmclock.AnalogAppWidgetProvider"); - return addAppWidget(db, values, cn, 2, 2, null); - } - - private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type, - SQLiteDatabase db, ContentValues values, TypedArray a, - PackageManager packageManager) throws XmlPullParserException, IOException { + private boolean addAppWidget(XmlResourceParser parser, int type, + SQLiteDatabase db, ContentValues values) + throws XmlPullParserException, IOException { - String packageName = a.getString(R.styleable.Favorite_packageName); - String className = a.getString(R.styleable.Favorite_className); + String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); + String className = getAttributeValue(parser, ATTR_CLASS_NAME); if (packageName == null || className == null) { return false; @@ -1399,21 +1831,25 @@ public class LauncherProvider extends ContentProvider { 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) { + System.out.println("Can't find widget provider: " + className); hasPackage = false; } } if (hasPackage) { - int spanX = a.getInt(R.styleable.Favorite_spanX, 0); - int spanY = a.getInt(R.styleable.Favorite_spanY, 0); + String spanX = getAttributeValue(parser, ATTR_SPAN_X); + String spanY = getAttributeValue(parser, ATTR_SPAN_Y); + + values.put(Favorites.SPANX, spanX); + values.put(Favorites.SPANY, spanY); // Read the extras Bundle extras = new Bundle(); @@ -1424,10 +1860,9 @@ public class LauncherProvider extends ContentProvider { continue; } - TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra); if (TAG_EXTRA.equals(parser.getName())) { - String key = ar.getString(R.styleable.Extra_key); - String value = ar.getString(R.styleable.Extra_value); + String key = getAttributeValue(parser, ATTR_KEY); + String value = getAttributeValue(parser, ATTR_VALUE); if (key != null && value != null) { extras.putString(key, value); } else { @@ -1436,16 +1871,16 @@ public class LauncherProvider extends ContentProvider { } else { throw new RuntimeException("Widgets can contain only extras"); } - ar.recycle(); } - return addAppWidget(db, values, cn, spanX, spanY, extras); + return addAppWidget(db, values, cn, extras); } return false; } + private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, - int spanX, int spanY, Bundle extras) { + Bundle extras) { boolean allocatedAppWidgets = false; final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); @@ -1453,8 +1888,6 @@ public class LauncherProvider extends ContentProvider { int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); - values.put(Favorites.SPANX, spanX); - values.put(Favorites.SPANY, spanY); values.put(Favorites.APPWIDGET_ID, appWidgetId); values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); values.put(Favorites._ID, generateNewItemId()); @@ -1480,17 +1913,15 @@ public class LauncherProvider extends ContentProvider { 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); @@ -1505,13 +1936,13 @@ public class LauncherProvider extends ContentProvider { 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) { @@ -1520,7 +1951,7 @@ public class LauncherProvider extends ContentProvider { return id; } - public void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { + private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { final ContentResolver resolver = mContext.getContentResolver(); Cursor c = null; int count = 0; @@ -1563,6 +1994,8 @@ public class LauncherProvider extends ContentProvider { = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); + final int profileIndex + = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); int i = 0; int curX = 0; @@ -1573,7 +2006,6 @@ public class LauncherProvider extends ContentProvider { final int width = (int) grid.numColumns; final int height = (int) grid.numRows; final int hotseatWidth = (int) grid.numHotseatIcons; - PackageManager pm = mContext.getPackageManager(); final HashSet<String> seenIntents = new HashSet<String>(c.getCount()); @@ -1594,6 +2026,19 @@ public class LauncherProvider extends ContentProvider { final int screen = c.getInt(screenIndex); int container = c.getInt(containerIndex); final String intentStr = c.getString(intentIndex); + + UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); + UserHandleCompat userHandle; + final long userSerialNumber; + if (profileIndex != -1 && !c.isNull(profileIndex)) { + userSerialNumber = c.getInt(profileIndex); + userHandle = userManager.getUserForSerialNumber(userSerialNumber); + } else { + // Default to the serial number of this user, for older + // shortcuts. + userHandle = UserHandleCompat.myUserHandle(); + userSerialNumber = userManager.getSerialNumberForUser(userHandle); + } Launcher.addDumpLog(TAG, "migrating \"" + c.getString(titleIndex) + "\" (" + cellX + "," + cellY + "@" @@ -1620,7 +2065,8 @@ public class LauncherProvider extends ContentProvider { Launcher.addDumpLog(TAG, "skipping empty intent", true); continue; } else if (cn != null && - !LauncherModel.isValidPackageComponent(pm, cn)) { + !LauncherModel.isValidPackageActivity(mContext, cn, + userHandle)) { // component no longer exists. Launcher.addDumpLog(TAG, "skipping item whose component " + "no longer exists.", true); @@ -1659,6 +2105,7 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); + values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { hotseat.put(screen, values); @@ -1792,7 +2239,7 @@ public class LauncherProvider extends ContentProvider { * Build a query string that will match any row where the column matches * anything in the values list. */ - static String buildOrWhereString(String column, int[] values) { + private static String buildOrWhereString(String column, int[] values) { StringBuilder selectWhere = new StringBuilder(); for (int i = values.length - 1; i >= 0; i--) { selectWhere.append(column).append("=").append(values[i]); @@ -1803,6 +2250,38 @@ public class LauncherProvider extends ContentProvider { return selectWhere.toString(); } + /** + * Return attribute value, attempting launcher-specific namespace first + * before falling back to anonymous attribute. + */ + private 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. + */ + private 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; @@ -1834,4 +2313,29 @@ public class LauncherProvider extends ContentProvider { } } } + + static interface WorkspaceLoader { + /** + * @param screenIds A mutable list of screen its + * @return the number of workspace items added. + */ + int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds); + } + + private static class SimpleWorkspaceLoader implements WorkspaceLoader { + private final Resources mRes; + private final int mWorkspaceId; + private final DatabaseHelper mHelper; + + SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) { + mHelper = helper; + mRes = res; + mWorkspaceId = workspaceId; + } + + @Override + public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) { + return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds); + } + } } |