diff options
author | Daniel Sandler <dsandler@android.com> | 2013-06-05 22:57:57 -0400 |
---|---|---|
committer | Daniel Sandler <dsandler@android.com> | 2013-06-05 23:30:20 -0400 |
commit | 325dc23624160689e59fbac708cf6f222b20d025 (patch) | |
tree | 3c6a13a52a6e5688c7e4404890e5e8f88d544856 /src/com/android/launcher3/LauncherProvider.java | |
parent | b582cd201fccece65d36b8915cf84fef3546cffa (diff) | |
download | android_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.tar.gz android_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.tar.bz2 android_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.zip |
Launcher2 is now Launcher3.
Changes include
- moving from com.android.launcher{,2} to
com.android.launcher3
- removing wallpapers
- new temporary icon
Change-Id: I1eabd06059e94a8f3bdf6b620777bd1d2b7c212b
Diffstat (limited to 'src/com/android/launcher3/LauncherProvider.java')
-rw-r--r-- | src/com/android/launcher3/LauncherProvider.java | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java new file mode 100644 index 000000000..fb12f7163 --- /dev/null +++ b/src/com/android/launcher3/LauncherProvider.java @@ -0,0 +1,1193 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.app.SearchManager; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.database.sqlite.SQLiteStatement; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +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.Xml; + +import com.android.launcher3.R; +import com.android.launcher3.LauncherSettings.Favorites; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +public class LauncherProvider extends ContentProvider { + private static final String TAG = "Launcher.LauncherProvider"; + private static final boolean LOGD = false; + + private static final String DATABASE_NAME = "launcher.db"; + + private static final int DATABASE_VERSION = 12; + + static final String AUTHORITY = "com.android.launcher3.settings"; + + static final String TABLE_FAVORITES = "favorites"; + static final String PARAMETER_NOTIFY = "notify"; + static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED = + "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED"; + static final String DEFAULT_WORKSPACE_RESOURCE_ID = + "DEFAULT_WORKSPACE_RESOURCE_ID"; + + private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE = + "com.android.launcher3.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE"; + + /** + * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when + * {@link AppWidgetHost#deleteHost()} is called during database creation. + * Use this to recall {@link AppWidgetHost#startListening()} if needed. + */ + static final Uri CONTENT_APPWIDGET_RESET_URI = + Uri.parse("content://" + AUTHORITY + "/appWidgetReset"); + + private DatabaseHelper mOpenHelper; + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + ((LauncherApplication) getContext()).setLauncherProvider(this); + return true; + } + + @Override + public String getType(Uri uri) { + SqlArguments args = new SqlArguments(uri, null, null); + if (TextUtils.isEmpty(args.where)) { + return "vnd.android.cursor.dir/" + args.table; + } else { + return "vnd.android.cursor.item/" + args.table; + } + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(args.table); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); + result.setNotificationUri(getContext().getContentResolver(), uri); + + return result; + } + + private static long dbInsertAndCheck(DatabaseHelper helper, + SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { + if (!values.containsKey(LauncherSettings.Favorites._ID)) { + throw new RuntimeException("Error: attempting to add item without specifying an id"); + } + return db.insert(table, nullColumnHack, values); + } + + private static void deleteId(SQLiteDatabase db, long id) { + Uri uri = LauncherSettings.Favorites.getContentUri(id, false); + SqlArguments args = new SqlArguments(uri, null, null); + db.delete(args.table, args.where, args.args); + } + + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + SqlArguments args = new SqlArguments(uri); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues); + if (rowId <= 0) return null; + + uri = ContentUris.withAppendedId(uri, rowId); + sendNotify(uri); + + return uri; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + SqlArguments args = new SqlArguments(uri); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + int numValues = values.length; + for (int i = 0; i < numValues; i++) { + if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) { + return 0; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + sendNotify(uri); + return values.length; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = db.delete(args.table, args.where, args.args); + if (count > 0) sendNotify(uri); + + return count; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + SqlArguments args = new SqlArguments(uri, selection, selectionArgs); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = db.update(args.table, values, args.where, args.args); + if (count > 0) sendNotify(uri); + + return count; + } + + private void sendNotify(Uri uri) { + String notify = uri.getQueryParameter(PARAMETER_NOTIFY); + if (notify == null || "true".equals(notify)) { + getContext().getContentResolver().notifyChange(uri, null); + } + } + + public long generateNewId() { + return mOpenHelper.generateNewId(); + } + + /** + * @param workspaceResId that can be 0 to use default or non-zero for specific resource + */ + synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); + if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) { + int workspaceResId = origWorkspaceResId; + + // Use default workspace resource if none provided + if (workspaceResId == 0) { + workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace); + } + + // Populate favorites table with initial favorites + SharedPreferences.Editor editor = sp.edit(); + editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED); + if (origWorkspaceResId != 0) { + editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId); + } + mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId); + editor.commit(); + } + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + 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_EXTRA = "extra"; + + private final Context mContext; + private final AppWidgetHost mAppWidgetHost; + private long mMaxId = -1; + + DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); + + // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from + // the DB here + if (mMaxId == -1) { + mMaxId = initializeMaxId(getWritableDatabase()); + } + } + + /** + * Send notification that we've deleted the {@link AppWidgetHost}, + * probably as part of the initial database creation. The receiver may + * want to re-call {@link AppWidgetHost#startListening()} to ensure + * callbacks are correctly set. + */ + private void sendAppWidgetResetNotify() { + final ContentResolver resolver = mContext.getContentResolver(); + resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null); + } + + @Override + public void onCreate(SQLiteDatabase db) { + if (LOGD) Log.d(TAG, "creating new launcher database"); + + mMaxId = 1; + + db.execSQL("CREATE TABLE favorites (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "intent TEXT," + + "container INTEGER," + + "screen INTEGER," + + "cellX INTEGER," + + "cellY INTEGER," + + "spanX INTEGER," + + "spanY INTEGER," + + "itemType INTEGER," + + "appWidgetId INTEGER NOT NULL DEFAULT -1," + + "isShortcut INTEGER," + + "iconType INTEGER," + + "iconPackage TEXT," + + "iconResource TEXT," + + "icon BLOB," + + "uri TEXT," + + "displayMode INTEGER" + + ");"); + + // Database was just created, so wipe any previous widgets + if (mAppWidgetHost != null) { + mAppWidgetHost.deleteHost(); + sendAppWidgetResetNotify(); + } + + if (!convertDatabase(db)) { + // Set a shared pref so that we know we need to load the default workspace later + setFlagToLoadDefaultWorkspaceLater(); + } + } + + private void setFlagToLoadDefaultWorkspaceLater() { + String spKey = LauncherApplication.getSharedPreferencesKey(); + SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true); + editor.commit(); + } + + private boolean convertDatabase(SQLiteDatabase db) { + if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade"); + boolean converted = false; + + final Uri uri = Uri.parse("content://" + Settings.AUTHORITY + + "/old_favorites?notify=true"); + final ContentResolver resolver = mContext.getContentResolver(); + Cursor cursor = null; + + try { + cursor = resolver.query(uri, null, null, null, null); + } catch (Exception e) { + // Ignore + } + + // We already have a favorites database in the old provider + if (cursor != null && cursor.getCount() > 0) { + try { + converted = copyFromCursor(db, cursor) > 0; + } finally { + cursor.close(); + } + + if (converted) { + resolver.delete(uri, null, null); + } + } + + if (converted) { + // Convert widgets from this import into widgets + if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade"); + convertWidgets(db); + } + + return converted; + } + + private int copyFromCursor(SQLiteDatabase db, Cursor c) { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); + + ContentValues[] rows = new ContentValues[c.getCount()]; + int i = 0; + while (c.moveToNext()) { + ContentValues values = new ContentValues(c.getColumnCount()); + values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); + values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); + values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); + values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); + values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); + values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); + values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); + values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex)); + values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex)); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); + values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex)); + values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex)); + values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex)); + values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); + values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex)); + rows[i++] = values; + } + + db.beginTransaction(); + int total = 0; + try { + int numValues = rows.length; + for (i = 0; i < numValues; i++) { + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) { + return 0; + } else { + total++; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return total; + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (LOGD) Log.d(TAG, "onUpgrade triggered"); + + int version = oldVersion; + if (version < 3) { + // upgrade 1,2 -> 3 added appWidgetId column + db.beginTransaction(); + try { + // Insert new column for holding appWidgetIds + db.execSQL("ALTER TABLE favorites " + + "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;"); + db.setTransactionSuccessful(); + version = 3; + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + // Convert existing widgets only if table upgrade was successful + if (version == 3) { + convertWidgets(db); + } + } + + if (version < 4) { + version = 4; + } + + // Where's version 5? + // - Donut and sholes on 2.0 shipped with version 4 of launcher1. + // - Passion shipped on 2.1 with version 6 of launcher3 + // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1 + // but version 5 on there was the updateContactsShortcuts change + // which was version 6 in launcher 2 (first shipped on passion 2.1r1). + // The updateContactsShortcuts change is idempotent, so running it twice + // is okay so we'll do that when upgrading the devices that shipped with it. + if (version < 6) { + // We went from 3 to 5 screens. Move everything 1 to the right + db.beginTransaction(); + try { + db.execSQL("UPDATE favorites SET screen=(screen + 1);"); + db.setTransactionSuccessful(); + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + + // We added the fast track. + if (updateContactsShortcuts(db)) { + version = 6; + } + } + + if (version < 7) { + // Version 7 gets rid of the special search widget. + convertWidgets(db); + version = 7; + } + + if (version < 8) { + // Version 8 (froyo) has the icons all normalized. This should + // already be the case in practice, but we now rely on it and don't + // resample the images each time. + normalizeIcons(db); + version = 8; + } + + if (version < 9) { + // The max id is not yet set at this point (onUpgrade is triggered in the ctor + // before it gets a change to get set, so we need to read it here when we use it) + if (mMaxId == -1) { + mMaxId = initializeMaxId(db); + } + + // Add default hotseat icons + loadFavorites(db, R.xml.update_workspace); + version = 9; + } + + // We bumped the version three time during JB, once to update the launch flags, once to + // update the override for the default launch animation and once to set the mimetype + // to improve startup performance + if (version < 12) { + // Contact shortcuts need a different set of flags to be launched now + // The updateContactsShortcuts change is idempotent, so we can keep using it like + // back in the Donut days + updateContactsShortcuts(db); + version = 12; + } + + if (version != DATABASE_VERSION) { + Log.w(TAG, "Destroying all old data."); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES); + onCreate(db); + } + } + + private boolean updateContactsShortcuts(SQLiteDatabase db) { + final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, + new int[] { Favorites.ITEM_TYPE_SHORTCUT }); + + Cursor c = null; + final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT"; + db.beginTransaction(); + try { + // Select and iterate through each matching widget + c = db.query(TABLE_FAVORITES, + new String[] { Favorites._ID, Favorites.INTENT }, + selectWhere, null, null, null, null); + if (c == null) return false; + + if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); + + final int idIndex = c.getColumnIndex(Favorites._ID); + final int intentIndex = c.getColumnIndex(Favorites.INTENT); + + while (c.moveToNext()) { + long favoriteId = c.getLong(idIndex); + final String intentUri = c.getString(intentIndex); + if (intentUri != null) { + try { + final Intent intent = Intent.parseUri(intentUri, 0); + android.util.Log.d("Home", intent.toString()); + final Uri uri = intent.getData(); + if (uri != null) { + final String data = uri.toString(); + if ((Intent.ACTION_VIEW.equals(intent.getAction()) || + actionQuickContact.equals(intent.getAction())) && + (data.startsWith("content://contacts/people/") || + data.startsWith("content://com.android.contacts/" + + "contacts/lookup/"))) { + + final Intent newIntent = new Intent(actionQuickContact); + // When starting from the launcher, start in a new, cleared task + // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we + // clear the whole thing preemptively here since + // QuickContactActivity will finish itself when launching other + // detail activities. + newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK); + newIntent.putExtra( + Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); + newIntent.setData(uri); + // Determine the type and also put that in the shortcut + // (that can speed up launch a bit) + newIntent.setDataAndType(uri, newIntent.resolveType(mContext)); + + final ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.INTENT, + newIntent.toUri(0)); + + String updateWhere = Favorites._ID + "=" + favoriteId; + db.update(TABLE_FAVORITES, values, updateWhere, null); + } + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem upgrading shortcut", ex); + } catch (URISyntaxException e) { + Log.e(TAG, "Problem upgrading shortcut", e); + } + } + } + + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while upgrading contacts", ex); + return false; + } finally { + db.endTransaction(); + if (c != null) { + c.close(); + } + } + + return true; + } + + private void normalizeIcons(SQLiteDatabase db) { + Log.d(TAG, "normalizing icons"); + + db.beginTransaction(); + Cursor c = null; + SQLiteStatement update = null; + try { + boolean logged = false; + update = db.compileStatement("UPDATE favorites " + + "SET icon=? WHERE _id=?"); + + c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" + + Favorites.ICON_TYPE_BITMAP, null); + + final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); + final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); + + while (c.moveToNext()) { + long id = c.getLong(idIndex); + byte[] data = c.getBlob(iconIndex); + try { + Bitmap bitmap = Utilities.resampleIconBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length), + mContext); + if (bitmap != null) { + update.bindLong(1, id); + data = ItemInfo.flattenBitmap(bitmap); + if (data != null) { + update.bindBlob(2, data); + update.execute(); + } + bitmap.recycle(); + } + } catch (Exception e) { + if (!logged) { + Log.e(TAG, "Failed normalizing icon " + id, e); + } else { + Log.e(TAG, "Also failed normalizing icon " + id); + } + logged = true; + } + } + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); + } finally { + db.endTransaction(); + if (update != null) { + update.close(); + } + if (c != null) { + c.close(); + } + } + } + + // Generates a new ID to use for an object in your database. This method should be only + // called from the main UI thread. As an exception, we do call it when we call the + // 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 + public long generateNewId() { + if (mMaxId < 0) { + throw new RuntimeException("Error: max id was not initialized"); + } + mMaxId += 1; + return mMaxId; + } + + private long initializeMaxId(SQLiteDatabase db) { + Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null); + + // get the result + final int maxIdIndex = 0; + long id = -1; + if (c != null && c.moveToNext()) { + id = c.getLong(maxIdIndex); + } + if (c != null) { + c.close(); + } + + if (id == -1) { + throw new RuntimeException("Error: could not query max id"); + } + + return id; + } + + /** + * Upgrade existing clock and photo frame widgets into their new widget + * equivalents. + */ + private void convertWidgets(SQLiteDatabase db) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + final int[] bindSources = new int[] { + Favorites.ITEM_TYPE_WIDGET_CLOCK, + Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME, + Favorites.ITEM_TYPE_WIDGET_SEARCH, + }; + + final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources); + + Cursor c = null; + + db.beginTransaction(); + try { + // Select and iterate through each matching widget + c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE }, + selectWhere, null, null, null, null); + + if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount()); + + final ContentValues values = new ContentValues(); + while (c != null && c.moveToNext()) { + long favoriteId = c.getLong(0); + int favoriteType = c.getInt(1); + + // Allocate and update database with new appWidgetId + try { + int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); + + if (LOGD) { + Log.d(TAG, "allocated appWidgetId=" + appWidgetId + + " for favoriteId=" + favoriteId); + } + values.clear(); + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET); + values.put(Favorites.APPWIDGET_ID, appWidgetId); + + // Original widgets might not have valid spans when upgrading + if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { + values.put(LauncherSettings.Favorites.SPANX, 4); + values.put(LauncherSettings.Favorites.SPANY, 1); + } else { + values.put(LauncherSettings.Favorites.SPANX, 2); + values.put(LauncherSettings.Favorites.SPANY, 2); + } + + String updateWhere = Favorites._ID + "=" + favoriteId; + db.update(TABLE_FAVORITES, values, updateWhere, null); + + if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + new ComponentName("com.android.alarmclock", + "com.android.alarmclock.AnalogAppWidgetProvider")); + } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + new ComponentName("com.android.camera", + "com.android.camera.PhotoAppWidgetProvider")); + } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) { + // TODO: check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, + getSearchWidgetProvider()); + } + } catch (RuntimeException ex) { + Log.e(TAG, "Problem allocating appWidgetId", ex); + } + } + + db.setTransactionSuccessful(); + } catch (SQLException ex) { + Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex); + } finally { + db.endTransaction(); + if (c != null) { + c.close(); + } + } + } + + 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); + } + } + + /** + * 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 + */ + private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) { + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + ContentValues values = new ContentValues(); + + PackageManager packageManager = mContext.getPackageManager(); + int allAppsButtonRank = + mContext.getResources().getInteger(R.integer.hotseat_all_apps_index); + int i = 0; + try { + XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId); + AttributeSet attrs = Xml.asAttributeSet(parser); + beginDocument(parser, TAG_FAVORITES); + + final int depth = parser.getDepth(); + + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + boolean added = false; + final String name = parser.getName(); + + 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 screen = a.getString(R.styleable.Favorite_screen); + String x = a.getString(R.styleable.Favorite_x); + String y = a.getString(R.styleable.Favorite_y); + + // If we are adding to the hotseat, the screen is used as the position in the + // hotseat. This screen can't be at position 0 because AllApps is in the + // zeroth position. + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + && Integer.valueOf(screen) == allAppsButtonRank) { + throw new RuntimeException("Invalid screen position for hotseat item"); + } + + values.clear(); + values.put(LauncherSettings.Favorites.CONTAINER, container); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, x); + values.put(LauncherSettings.Favorites.CELLY, y); + + if (TAG_FAVORITE.equals(name)) { + long id = addAppShortcut(db, values, a, packageManager, intent); + 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); + } else if (TAG_SHORTCUT.equals(name)) { + long id = addUriShortcut(db, values, a); + 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(); + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > folderDepth) { + 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 (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++; + a.recycle(); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } catch (IOException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } catch (RuntimeException e) { + Log.w(TAG, "Got exception parsing favorites.", e); + } + + return i; + } + + 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); + try { + ComponentName cn; + 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); + } + id = generateNewId(); + 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, generateNewId()); + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { + return -1; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to add favorite: " + packageName + + "/" + className, e); + } + return id; + } + + private long addFolder(SQLiteDatabase db, ContentValues values) { + values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER); + values.put(Favorites.SPANX, 1); + values.put(Favorites.SPANY, 1); + long id = generateNewId(); + values.put(Favorites._ID, id); + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) { + return -1; + } else { + return id; + } + } + + private ComponentName getSearchWidgetProvider() { + SearchManager searchManager = + (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + ComponentName searchComponent = searchManager.getGlobalSearchActivity(); + if (searchComponent == null) return null; + return getProviderInPackage(searchComponent.getPackageName()); + } + + /** + * Gets an appwidget provider from the given package. If the package contains more than + * one appwidget provider, an arbitrary one is returned. + */ + private ComponentName getProviderInPackage(String packageName) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders(); + if (providers == null) return null; + final int providerCount = providers.size(); + for (int i = 0; i < providerCount; i++) { + ComponentName provider = providers.get(i).provider; + if (provider != null && provider.getPackageName().equals(packageName)) { + return provider; + } + } + 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 { + + String packageName = a.getString(R.styleable.Favorite_packageName); + String className = a.getString(R.styleable.Favorite_className); + + if (packageName == null || className == null) { + return false; + } + + boolean hasPackage = true; + ComponentName cn = new ComponentName(packageName, className); + try { + packageManager.getReceiverInfo(cn, 0); + } catch (Exception e) { + String[] packages = packageManager.currentToCanonicalPackageNames( + new String[] { packageName }); + cn = new ComponentName(packages[0], className); + try { + packageManager.getReceiverInfo(cn, 0); + } catch (Exception e1) { + hasPackage = false; + } + } + + if (hasPackage) { + int spanX = a.getInt(R.styleable.Favorite_spanX, 0); + int spanY = a.getInt(R.styleable.Favorite_spanY, 0); + + // Read the extras + Bundle extras = new Bundle(); + int widgetDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > widgetDepth) { + if (type != XmlPullParser.START_TAG) { + 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); + 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"); + } + ar.recycle(); + } + + return addAppWidget(db, values, cn, spanX, spanY, extras); + } + + return false; + } + + private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, + int spanX, int spanY, Bundle extras) { + boolean allocatedAppWidgets = false; + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + + try { + 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._ID, generateNewId()); + dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values); + + allocatedAppWidgets = true; + + // TODO: need to check return value + appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn); + + // Send a broadcast to configure the widget + if (extras != null && !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) { + Log.e(TAG, "Problem allocating appWidgetId", ex); + } + + 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); + + Intent intent; + String uri = null; + try { + uri = a.getString(R.styleable.Favorite_uri); + intent = Intent.parseUri(uri, 0); + } catch (URISyntaxException e) { + Log.w(TAG, "Shortcut has malformed uri: " + uri); + return -1; // Oh well + } + + if (iconResId == 0 || titleResId == 0) { + Log.w(TAG, "Shortcut is missing title or icon resource ID"); + return -1; + } + + long id = generateNewId(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + values.put(Favorites.INTENT, intent.toUri(0)); + values.put(Favorites.TITLE, r.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._ID, id); + + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) { + return -1; + } + return id; + } + } + + /** + * 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) { + StringBuilder selectWhere = new StringBuilder(); + for (int i = values.length - 1; i >= 0; i--) { + selectWhere.append(column).append("=").append(values[i]); + if (i > 0) { + selectWhere.append(" OR "); + } + } + return selectWhere.toString(); + } + + static class SqlArguments { + public final String table; + public final String where; + public final String[] args; + + SqlArguments(Uri url, String where, String[] args) { + if (url.getPathSegments().size() == 1) { + this.table = url.getPathSegments().get(0); + this.where = where; + this.args = args; + } else if (url.getPathSegments().size() != 2) { + throw new IllegalArgumentException("Invalid URI: " + url); + } else if (!TextUtils.isEmpty(where)) { + throw new UnsupportedOperationException("WHERE clause not supported: " + url); + } else { + this.table = url.getPathSegments().get(0); + this.where = "_id=" + ContentUris.parseId(url); + this.args = null; + } + } + + SqlArguments(Uri url) { + if (url.getPathSegments().size() == 1) { + table = url.getPathSegments().get(0); + where = null; + args = null; + } else { + throw new IllegalArgumentException("Invalid URI: " + url); + } + } + } +} |