From 0fe505bf82a265e51c556d7204976651cde7f55c Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 6 Aug 2014 09:55:36 -0700 Subject: Autoinstalls loading xml > Launcher checkes for an apk in the system image with a broadcast receiver for action: com.android.launcher3.action.LAUNCHER_CUSTOMIZATION > Default layout is parsed from that apk, which can also contain icons and string resources used in the layout config Change-Id: I44fc9e7c3134f525f7b5db29f4e8bb56e17ce445 --- src/com/android/launcher3/AutoInstallsLayout.java | 564 ++++++++++++++++++++++ src/com/android/launcher3/LauncherModel.java | 2 +- src/com/android/launcher3/LauncherProvider.java | 99 ++-- src/com/android/launcher3/PreloadReceiver.java | 51 -- src/com/android/launcher3/Utilities.java | 27 +- 5 files changed, 650 insertions(+), 93 deletions(-) create mode 100644 src/com/android/launcher3/AutoInstallsLayout.java delete mode 100644 src/com/android/launcher3/PreloadReceiver.java (limited to 'src/com') diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java new file mode 100644 index 000000000..4ea7b3d78 --- /dev/null +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -0,0 +1,564 @@ +/* + * 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.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.Patterns; + +import com.android.launcher3.LauncherProvider.SqlArguments; +import com.android.launcher3.LauncherProvider.WorkspaceLoader; +import com.android.launcher3.LauncherSettings.Favorites; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This class contains contains duplication of functionality as found in + * LauncherProvider#DatabaseHelper. It has been isolated and differentiated in order + * to cleanly and separately represent AutoInstall default layout format and policy. + */ +public class AutoInstallsLayout implements WorkspaceLoader { + private static final String TAG = "AutoInstalls"; + private static final boolean LOGD = true; + + /** Marker action used to discover a package which defines launcher customization */ + static final String ACTION_LAUNCHER_CUSTOMIZATION = + "com.android.launcher3.action.LAUNCHER_CUSTOMIZATION"; + + private static final String LAYOUT_RES = "default_layout"; + + static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost, + LayoutParserCallback callback) { + Pair customizationApkInfo = Utilities.findSystemApk( + ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); + if (customizationApkInfo == null) { + return null; + } + + String pkg = customizationApkInfo.first; + Resources res = customizationApkInfo.second; + int layoutId = res.getIdentifier(LAYOUT_RES, "xml", pkg); + if (layoutId == 0) { + Log.e(TAG, "Layout definition not found in package: " + pkg); + return null; + } + return new AutoInstallsLayout(context, appWidgetHost, callback, pkg, res, layoutId); + } + + // Object Tags + private static final String TAG_WORKSPACE = "workspace"; + private static final String TAG_APP_ICON = "appicon"; + private static final String TAG_AUTO_INSTALL = "autoinstall"; + private static final String TAG_FOLDER = "folder"; + private static final String TAG_APPWIDGET = "appwidget"; + private static final String TAG_SHORTCUT = "shortcut"; + private static final String TAG_EXTRA = "extra"; + + private static final String ATTR_CONTAINER = "container"; + private static final String ATTR_RANK = "rank"; + + private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_CLASS_NAME = "className"; + private static final String ATTR_TITLE = "title"; + 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_URL = "url"; + + // Style attrs -- "Extra" + private static final String ATTR_KEY = "key"; + private static final String ATTR_VALUE = "value"; + + private static final String HOTSEAT_CONTAINER_NAME = + Favorites.containerToString(Favorites.CONTAINER_HOTSEAT); + + private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = + "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; + + private final Context mContext; + private final AppWidgetHost mAppWidgetHost; + private final LayoutParserCallback mCallback; + + private final PackageManager mPackageManager; + private final ContentValues mValues; + + private final Resources mRes; + private final int mLayoutId; + + private SQLiteDatabase mDb; + + public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost, + LayoutParserCallback callback, String packageName, Resources res, int layoutId) { + mContext = context; + mAppWidgetHost = appWidgetHost; + mCallback = callback; + + mPackageManager = context.getPackageManager(); + mValues = new ContentValues(); + + mRes = res; + mLayoutId = layoutId; + } + + @Override + public int loadLayout(SQLiteDatabase db, ArrayList screenIds) { + mDb = db; + try { + return parseLayout(mRes, mLayoutId, screenIds); + } catch (XmlPullParserException | IOException | RuntimeException e) { + Log.w(TAG, "Got exception parsing layout.", e); + return -1; + } + } + + private int parseLayout(Resources res, int layoutId, ArrayList screenIds) + throws XmlPullParserException, IOException { + final int hotseatAllAppsRank = LauncherAppState.getInstance() + .getDynamicGrid().getDeviceProfile().hotseatAllAppsRank; + + XmlResourceParser parser = res.getXml(layoutId); + beginDocument(parser, TAG_WORKSPACE); + final int depth = parser.getDepth(); + int type; + HashMap tagParserMap = getLayoutElementsMap(); + int count = 0; + + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + mValues.clear(); + final int container; + final long screenId; + + if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) { + container = Favorites.CONTAINER_HOTSEAT; + + // Hack: hotseat items are stored using screen ids + long rank = Long.parseLong(getAttributeValue(parser, ATTR_RANK)); + screenId = (rank < hotseatAllAppsRank) ? rank : (rank + 1); + + } else { + container = Favorites.CONTAINER_DESKTOP; + screenId = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN)); + + mValues.put(Favorites.CELLX, getAttributeValue(parser, ATTR_X)); + mValues.put(Favorites.CELLY, getAttributeValue(parser, ATTR_Y)); + } + + mValues.put(Favorites.CONTAINER, container); + mValues.put(Favorites.SCREEN, screenId); + + TagParser tagParser = tagParserMap.get(parser.getName()); + if (tagParser == null) { + if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName()); + continue; + } + long newElementId = tagParser.parseAndAdd(parser, res); + if (newElementId >= 0) { + // Keep track of the set of screens which need to be added to the db. + if (!screenIds.contains(screenId) && + container == Favorites.CONTAINER_DESKTOP) { + screenIds.add(screenId); + } + count++; + } + } + return count; + } + + protected long addShortcut(String title, Intent intent, int type) { + long id = mCallback.generateNewItemId(); + mValues.put(Favorites.INTENT, intent.toUri(0)); + mValues.put(Favorites.TITLE, title); + mValues.put(Favorites.ITEM_TYPE, type); + mValues.put(Favorites.SPANX, 1); + mValues.put(Favorites.SPANY, 1); + mValues.put(Favorites._ID, id); + if (mCallback.insertAndCheck(mDb, mValues) < 0) { + return -1; + } else { + return id; + } + } + + protected HashMap getFolderElementsMap() { + HashMap parsers = new HashMap(); + parsers.put(TAG_APP_ICON, new AppShortcutParser()); + parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser()); + parsers.put(TAG_SHORTCUT, new ShortcutParser()); + return parsers; + } + + protected HashMap getLayoutElementsMap() { + HashMap parsers = new HashMap(); + parsers.put(TAG_APP_ICON, new AppShortcutParser()); + parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser()); + parsers.put(TAG_FOLDER, new FolderParser()); + parsers.put(TAG_APPWIDGET, new AppWidgetParser()); + parsers.put(TAG_SHORTCUT, new ShortcutParser()); + return parsers; + } + + private interface TagParser { + /** + * Parses the tag and adds to the db + * @return the id of the row added or -1; + */ + long parseAndAdd(XmlResourceParser parser, Resources res) + throws XmlPullParserException, IOException; + } + + private class AppShortcutParser implements TagParser { + + @Override + public long parseAndAdd(XmlResourceParser parser, Resources res) { + final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); + final String className = getAttributeValue(parser, ATTR_CLASS_NAME); + + 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 = new Intent(Intent.ACTION_MAIN, null) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(cn) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + return addShortcut(info.loadLabel(mPackageManager).toString(), + intent, Favorites.ITEM_TYPE_APPLICATION); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e); + } + return -1; + } else { + if (LOGD) Log.d(TAG, "Skipping invalid with no component or uri"); + return -1; + } + } + } + + private class AutoInstallParser implements TagParser { + + @Override + public long parseAndAdd(XmlResourceParser parser, Resources res) { + final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); + final String className = getAttributeValue(parser, ATTR_CLASS_NAME); + if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { + if (LOGD) Log.d(TAG, "Skipping invalid with no component"); + return -1; + } + + mValues.put(Favorites.RESTORED, 1); + final Intent intent = new Intent(Intent.ACTION_MAIN, null) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(new ComponentName(packageName, className)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + return addShortcut(mContext.getString(R.string.package_state_unknown), intent, + Favorites.ITEM_TYPE_APPLICATION); + } + } + + private class ShortcutParser implements TagParser { + + @Override + public long parseAndAdd(XmlResourceParser parser, Resources res) { + final String url = getAttributeValue(parser, ATTR_URL); + final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0); + final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0); + + if (titleResId == 0 || iconId == 0) { + if (LOGD) Log.d(TAG, "Ignoring shortcut"); + return -1; + } + + if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) { + if (LOGD) Log.d(TAG, "Ignoring shortcut, invalid url: " + url); + return -1; + } + Drawable icon = res.getDrawable(iconId); + if (icon == null) { + if (LOGD) Log.d(TAG, "Ignoring shortcut, can't load icon"); + return -1; + } + + ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext)); + final Intent intent = new Intent(Intent.ACTION_VIEW, null) + .setData(Uri.parse(url)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + return addShortcut(res.getString(titleResId), intent, Favorites.ITEM_TYPE_SHORTCUT); + } + } + + private class AppWidgetParser implements TagParser { + + @Override + public long parseAndAdd(XmlResourceParser parser, Resources res) + throws XmlPullParserException, IOException { + final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); + final String className = getAttributeValue(parser, ATTR_CLASS_NAME); + if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { + if (LOGD) Log.d(TAG, "Skipping invalid with no component"); + return -1; + } + + ComponentName cn = new ComponentName(packageName, className); + try { + mPackageManager.getReceiverInfo(cn, 0); + } catch (Exception e) { + String[] packages = mPackageManager.currentToCanonicalPackageNames( + new String[] { packageName }); + cn = new ComponentName(packages[0], className); + try { + mPackageManager.getReceiverInfo(cn, 0); + } catch (Exception e1) { + if (LOGD) Log.d(TAG, "Can't find widget provider: " + className); + return -1; + } + } + + mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X)); + mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y)); + + // Read the extras + Bundle extras = new Bundle(); + int widgetDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > widgetDepth) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (TAG_EXTRA.equals(parser.getName())) { + String key = getAttributeValue(parser, ATTR_KEY); + String value = getAttributeValue(parser, ATTR_VALUE); + if (key != null && value != null) { + extras.putString(key, value); + } else { + throw new RuntimeException("Widget extras must have a key and value"); + } + } else { + throw new RuntimeException("Widgets can contain only extras"); + } + } + + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + long insertedId = -1; + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) { + if (LOGD) Log.e(TAG, "Unable to bind app widget id " + cn); + return -1; + } + + mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); + mValues.put(Favorites.APPWIDGET_ID, appWidgetId); + mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); + mValues.put(Favorites._ID, mCallback.generateNewItemId()); + insertedId = mCallback.insertAndCheck(mDb, mValues); + if (insertedId < 0) { + mAppWidgetHost.deleteAppWidgetId(appWidgetId); + return insertedId; + } + + // Send a broadcast to configure the widget + if (!extras.isEmpty()) { + Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE); + intent.setComponent(cn); + intent.putExtras(extras); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + mContext.sendBroadcast(intent); + } + } catch (RuntimeException ex) { + if (LOGD) Log.e(TAG, "Problem allocating appWidgetId", ex); + } + return insertedId; + } + } + + private class FolderParser implements TagParser { + private final HashMap mFolderElements = getFolderElementsMap(); + + @Override + public long parseAndAdd(XmlResourceParser parser, Resources res) + throws XmlPullParserException, IOException { + 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); + } + + mValues.put(Favorites.TITLE, title); + mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); + mValues.put(Favorites.SPANX, 1); + mValues.put(Favorites.SPANY, 1); + mValues.put(Favorites._ID, mCallback.generateNewItemId()); + long folderId = mCallback.insertAndCheck(mDb, mValues); + if (folderId < 0) { + if (LOGD) Log.e(TAG, "Unable to add folder"); + return -1; + } + + final ContentValues myValues = new ContentValues(mValues); + ArrayList folderItems = new ArrayList(); + + int type; + int folderDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > folderDepth) { + if (type != XmlPullParser.START_TAG) { + continue; + } + mValues.clear(); + mValues.put(Favorites.CONTAINER, folderId); + + TagParser tagParser = mFolderElements.get(parser.getName()); + if (tagParser != null) { + final long id = tagParser.parseAndAdd(parser, res); + if (id >= 0) { + folderItems.add(id); + } + } else { + throw new RuntimeException("Invalid folder item " + parser.getName()); + } + } + + long addedId = 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) { + // Delete the folder + Uri uri = Favorites.getContentUri(folderId, false); + SqlArguments args = new SqlArguments(uri, null, null); + mDb.delete(args.table, args.where, args.args); + addedId = -1; + + // 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(myValues, childValues, Favorites.CONTAINER); + copyInteger(myValues, childValues, Favorites.SCREEN); + copyInteger(myValues, childValues, Favorites.CELLX); + copyInteger(myValues, childValues, Favorites.CELLY); + + addedId = folderItems.get(0); + mDb.update(LauncherProvider.TABLE_FAVORITES, childValues, + Favorites._ID + "=" + addedId, null); + } + } + return addedId; + } + } + + private static final void beginDocument(XmlPullParser parser, String firstElementName) + throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT); + + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + /** + * 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; + } + + public static interface LayoutParserCallback { + long generateNewItemId(); + + long insertAndCheck(SQLiteDatabase db, ContentValues values); + } + + private static void copyInteger(ContentValues from, ContentValues to, String key) { + to.put(key, from.getAsInteger(key)); + } +} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 5c668d65f..109a70090 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -1860,7 +1860,7 @@ public class LauncherModel extends BroadcastReceiver } else { // Make sure the default workspace is loaded Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false); - LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); + LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(); } // This code path is for our old migration code and should no longer be exercised diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 88ea45a43..af655367a 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -33,11 +33,9 @@ import android.content.OperationApplicationException; 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; -import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.Cursor; import android.database.SQLException; @@ -51,15 +49,14 @@ 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 com.android.launcher3.LauncherSettings.Favorites; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -93,8 +90,6 @@ 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"; @@ -313,41 +308,41 @@ public class LauncherProvider extends ContentProvider { } /** - * @param workspaceResId that can be 0 to use default or non-zero for specific resource + * 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(int origWorkspaceResId) { + 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"); - // By default we use our resources - Resources res = getContext().getResources(); - int workspaceResId = origWorkspaceResId; - // Use default workspace resource if none provided - if (workspaceResId == 0) { + 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(); - workspaceResId = partnerRes.getIdentifier(Partner.RESOURCE_DEFAULT_LAYOUT, + int workspaceResId = partnerRes.getIdentifier(Partner.RESOURCE_DEFAULT_LAYOUT, "xml", partner.getPackageName()); - res = partnerRes; + if (workspaceResId != 0) { + loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId); + } } } - if (workspaceResId == 0) { - workspaceResId = - sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, getDefaultWorkspaceResourceId()); - } - // 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(), res, workspaceResId); + // Populate favorites table with initial favorites + SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED); + mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader); editor.commit(); } } @@ -389,12 +384,10 @@ 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"; @@ -789,7 +782,8 @@ public class LauncherProvider extends ContentProvider { } // Add default hotseat icons - loadFavorites(db, mContext.getResources(), R.xml.update_workspace); + loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(), + R.xml.update_workspace)); version = 9; } @@ -1084,6 +1078,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"); @@ -1092,6 +1087,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; } @@ -1365,9 +1365,10 @@ public class LauncherProvider extends ContentProvider { return intent; } - private int loadFavorites(SQLiteDatabase db, Resources res, int workspaceResourceId) { + private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) { ArrayList screenIds = new ArrayList(); - int count = loadFavoritesRecursive(db, res, workspaceResourceId, screenIds); + // 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); @@ -1922,7 +1923,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; @@ -1977,7 +1978,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 seenIntents = new HashSet(c.getCount()); @@ -2211,7 +2211,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]); @@ -2226,7 +2226,7 @@ public class LauncherProvider extends ContentProvider { * Return attribute value, attempting launcher-specific namespace first * before falling back to anonymous attribute. */ - static String getAttributeValue(XmlResourceParser parser, String 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) { @@ -2239,7 +2239,7 @@ public class LauncherProvider extends ContentProvider { * Return attribute resource value, attempting launcher-specific namespace * first before falling back to anonymous attribute. */ - static int getAttributeResourceValue(XmlResourceParser parser, String 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, @@ -2285,4 +2285,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 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 screenIds) { + return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds); + } + } } diff --git a/src/com/android/launcher3/PreloadReceiver.java b/src/com/android/launcher3/PreloadReceiver.java deleted file mode 100644 index ca25746eb..000000000 --- a/src/com/android/launcher3/PreloadReceiver.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2012 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.util.Log; - -public class PreloadReceiver extends BroadcastReceiver { - private static final String TAG = "Launcher.PreloadReceiver"; - private static final boolean LOGD = false; - - public static final String EXTRA_WORKSPACE_NAME = - "com.android.launcher3.action.EXTRA_WORKSPACE_NAME"; - - @Override - public void onReceive(Context context, Intent intent) { - final LauncherProvider provider = LauncherAppState.getLauncherProvider(); - if (provider != null) { - String name = intent.getStringExtra(EXTRA_WORKSPACE_NAME); - final int workspaceResId = !TextUtils.isEmpty(name) - ? context.getResources().getIdentifier(name, "xml", "com.android.launcher3") : 0; - if (LOGD) { - Log.d(TAG, "workspace name: " + name + " id: " + workspaceResId); - } - new AsyncTask() { - public Void doInBackground(Void ... args) { - provider.loadDefaultFavoritesIfNecessary(workspaceResId); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); - } - } -} diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 87c2d7515..60185c658 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -38,8 +38,8 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; import android.os.Build; -import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.view.View; import android.widget.Toast; @@ -328,9 +328,6 @@ public final class Utilities { private static void initStatics(Context context) { final Resources resources = context.getResources(); - final DisplayMetrics metrics = resources.getDisplayMetrics(); - final float density = metrics.density; - sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size); sIconTextureWidth = sIconTextureHeight = sIconWidth; } @@ -474,4 +471,26 @@ public final class Utilities { } return bestColor; } + + /* + * Finds a system apk which had a broadcast receiver listening to a particular action. + * @param action intent action used to find the apk + * @return a pair of apk package name and the resources. + */ + static Pair findSystemApk(String action, PackageManager pm) { + final Intent intent = new Intent(action); + 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); + return Pair.create(packageName, res); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + } + return null; + } } -- cgit v1.2.3