Files
Lawnchair/src/com/android/launcher3/LauncherProvider.java
T
Dan Sandler ab5fa3a06a Don't lose icons when migrating from different-sized hotseats.
Launcher2 on sw720dp devices had 8 hotseat slots; Launcher3
has only 6 on those same devices. When importing the old
hotseat, the empty slot (so technically there were 9)
occupied by the all apps button didn't line up with the new
all apps button, causing a hole on the hotseat. Furthermore,
the icon in old position 3 was clobbered by the new all apps
icon:

    0 1 2 3 (#) 4 5 6 7 ==> 0 1 2 (:) _ 4 5

In this CL we introduce a separate sort-and-place step for
hotseat icons so that any icon colliding with the new
all-apps slot can be moved to the right to find the next
nearest open slot. This works well for the 8->6 case; it
will probably do something reasonable, if not ideal, for
other grid migration scenarios.

But those are not covered by this bug.

Bug: 13015468
Change-Id: I7edb3f27addc78ad02bbfcd1c39175ca56220007
2014-03-06 23:54:24 -05:00

1838 lines
82 KiB
Java

/*
* 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.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
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.SparseArray;
import android.util.Xml;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.ProviderConfig;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
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 = 17;
static final String OLD_AUTHORITY = "com.android.launcher2.settings";
static final String AUTHORITY = ProviderConfig.AUTHORITY;
// Should we attempt to load anything from the com.android.launcher2 provider?
static final boolean IMPORT_LAUNCHER2_DATABASE = false;
static final String TABLE_FAVORITES = "favorites";
static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
static final String PARAMETER_NOTIFY = "notify";
static final String UPGRADED_FROM_OLD_DATABASE =
"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";
/**
* {@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;
private static boolean sJustLoadedFromOldDb;
@Override
public boolean onCreate() {
final Context context = getContext();
mOpenHelper = new DatabaseHelper(context);
LauncherAppState.setLauncherProvider(this);
return true;
}
public boolean wasNewDbCreated() {
return mOpenHelper.wasNewDbCreated();
}
@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 == null) {
throw new RuntimeException("Error: attempting to insert null values");
}
if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
helper.checkId(table, values);
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();
addModifiedTime(initialValues);
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++) {
addModifiedTime(values[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 ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
ContentProviderResult[] result = super.applyBatch(operations);
db.setTransactionSuccessful();
return result;
} finally {
db.endTransaction();
}
}
@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);
addModifiedTime(values);
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);
}
// always notify the backup agent
LauncherBackupAgentHelper.dataChanged(getContext());
}
private void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
}
public long generateNewItemId() {
return mOpenHelper.generateNewItemId();
}
public void updateMaxItemId(long id) {
mOpenHelper.updateMaxItemId(id);
}
public long generateNewScreenId() {
return mOpenHelper.generateNewScreenId();
}
// This is only required one time while loading the workspace during the
// upgrade path, and should never be called from anywhere else.
public void updateMaxScreenId(long maxScreenId) {
mOpenHelper.updateMaxScreenId(maxScreenId);
}
/**
* @param Should we load the old db for upgrade? first run only.
*/
synchronized public boolean justLoadedOldDb() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
boolean loadedOldDb = false || sJustLoadedFromOldDb;
sJustLoadedFromOldDb = false;
if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
SharedPreferences.Editor editor = sp.edit();
editor.remove(UPGRADED_FROM_OLD_DATABASE);
editor.commit();
loadedOldDb = true;
}
return loadedOldDb;
}
/**
* @param workspaceResId that can be 0 to use default or non-zero for specific resource
*/
synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
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());
}
// 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);
}
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
mOpenHelper.setFlagJustLoadedOldDb();
editor.commit();
}
}
public void migrateLauncher2Shortcuts() {
mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
LauncherSettings.Favorites.OLD_CONTENT_URI);
}
private static int getDefaultWorkspaceResourceId() {
if (LauncherAppState.isDisableAllApps()) {
return R.xml.default_workspace_no_all_apps;
} else {
return R.xml.default_workspace;
}
}
private static interface ContentValuesCallback {
public void onRow(ContentValues values);
}
private static boolean shouldImportLauncher2Database(Context context) {
boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
// We don't import the old databse for tablets, as the grid size has changed.
return !isTablet && IMPORT_LAUNCHER2_DATABASE;
}
public void deleteDatabase() {
// Are you sure? (y/n)
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final File dbFile = new File(db.getPath());
mOpenHelper.close();
if (dbFile.exists()) {
SQLiteDatabase.deleteDatabase(dbFile);
}
mOpenHelper = new DatabaseHelper(getContext());
}
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 static final String TAG_INCLUDE = "include";
private final Context mContext;
private final AppWidgetHost mAppWidgetHost;
private long mMaxItemId = -1;
private long mMaxScreenId = -1;
private boolean mNewDbCreated = false;
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 (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(getWritableDatabase());
}
if (mMaxScreenId == -1) {
mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
}
}
public boolean wasNewDbCreated() {
return mNewDbCreated;
}
/**
* 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");
mMaxItemId = 1;
mMaxScreenId = 0;
mNewDbCreated = true;
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," +
"appWidgetProvider TEXT," +
"modified INTEGER NOT NULL DEFAULT 0," +
"restored INTEGER NOT NULL DEFAULT 0" +
");");
addWorkspacesTable(db);
// Database was just created, so wipe any previous widgets
if (mAppWidgetHost != null) {
mAppWidgetHost.deleteHost();
sendAppWidgetResetNotify();
}
if (shouldImportLauncher2Database(mContext)) {
// Try converting the old database
ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
public void onRow(ContentValues values) {
int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
if (container == Favorites.CONTAINER_DESKTOP) {
int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
screen = (int) upgradeLauncherDb_permuteScreens(screen);
values.put(LauncherSettings.Favorites.SCREEN, screen);
}
}
};
Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
"/old_favorites?notify=true");
if (!convertDatabase(db, uri, permuteScreensCb, true)) {
// Try and upgrade from the Launcher2 db
uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
if (!convertDatabase(db, uri, permuteScreensCb, false)) {
// If we fail, then set a flag to load the default workspace
setFlagEmptyDbCreated();
return;
}
}
// Right now, in non-default workspace cases, we want to run the final
// upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
// set that flag too.
setFlagJustLoadedOldDb();
} else {
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
setFlagEmptyDbCreated();
}
}
private void addWorkspacesTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
");");
}
private void setFlagJustLoadedOldDb() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
editor.putBoolean(EMPTY_DATABASE_CREATED, false);
editor.commit();
}
private void setFlagEmptyDbCreated() {
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(EMPTY_DATABASE_CREATED, true);
editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
editor.commit();
}
// We rearrange the screens from the old launcher
// 12345 -> 34512
private long upgradeLauncherDb_permuteScreens(long screen) {
if (screen >= 2) {
return screen - 2;
} else {
return screen + 3;
}
}
private boolean convertDatabase(SQLiteDatabase db, Uri uri,
ContentValuesCallback cb, boolean deleteRows) {
if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
boolean converted = false;
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) {
try {
if (cursor.getCount() > 0) {
converted = copyFromCursor(db, cursor, cb) > 0;
if (converted && deleteRows) {
resolver.delete(uri, null, null);
}
}
} finally {
cursor.close();
}
}
if (converted) {
// Convert widgets from this import into widgets
if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
convertWidgets(db);
// Update max item id
mMaxItemId = initializeMaxItemId(db);
if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
}
return converted;
}
private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
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));
if (cb != null) {
cb.onRow(values);
}
rows[i++] = values;
}
int total = 0;
if (i > 0) {
db.beginTransaction();
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: " + oldVersion);
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 (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(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 < 13) {
// With the new shrink-wrapped and re-orderable workspaces, it makes sense
// to persist workspace screens and their relative order.
mMaxScreenId = 0;
// This will never happen in the wild, but when we switch to using workspace
// screen ids, redo the import from old launcher.
sJustLoadedFromOldDb = true;
addWorkspacesTable(db);
version = 13;
}
if (version < 14) {
db.beginTransaction();
try {
// Insert new column for holding widget provider name
db.execSQL("ALTER TABLE favorites " +
"ADD COLUMN appWidgetProvider TEXT;");
db.setTransactionSuccessful();
version = 14;
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
} finally {
db.endTransaction();
}
}
if (version < 15) {
db.beginTransaction();
try {
// Insert new column for holding update timestamp
db.execSQL("ALTER TABLE favorites " +
"ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
db.execSQL("ALTER TABLE workspaceScreens " +
"ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
db.setTransactionSuccessful();
version = 15;
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
} finally {
db.endTransaction();
}
}
if (version < 16) {
db.beginTransaction();
try {
// Insert new column for holding restore status
db.execSQL("ALTER TABLE favorites " +
"ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
db.setTransactionSuccessful();
version = 16;
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
} finally {
db.endTransaction();
}
}
if (version < 17) {
// We use the db version upgrade here to identify users who may not have seen
// clings yet (because they weren't available), but for whom the clings are now
// available (tablet users). Because one of the possible cling flows (migration)
// is very destructive (wipes out workspaces), we want to prevent this from showing
// until clear data. We do so by marking that the clings have been shown.
LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
version = 17;
}
if (version != DATABASE_VERSION) {
Log.w(TAG, "Destroying all old data.");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
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 generateNewItemId() {
if (mMaxItemId < 0) {
throw new RuntimeException("Error: max item id was not initialized");
}
mMaxItemId += 1;
return mMaxItemId;
}
public void updateMaxItemId(long id) {
mMaxItemId = id + 1;
}
public void checkId(String table, ContentValues values) {
long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
mMaxScreenId = Math.max(id, mMaxScreenId);
} else {
mMaxItemId = Math.max(id, mMaxItemId);
}
}
private long initializeMaxItemId(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 item id");
}
return id;
}
// Generates a new ID to use for an workspace screen 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 generateNewScreenId() {
if (mMaxScreenId < 0) {
throw new RuntimeException("Error: max screen id was not initialized");
}
mMaxScreenId += 1;
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
return mMaxScreenId;
}
public void updateMaxScreenId(long maxScreenId) {
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
mMaxScreenId = maxScreenId;
}
private long initializeMaxScreenId(SQLiteDatabase db) {
Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, 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 screen id");
}
// Log to disk
Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
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();
}
}
// Update max item id
mMaxItemId = initializeMaxItemId(db);
if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
}
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();
if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
PackageManager packageManager = mContext.getPackageManager();
int i = 0;
try {
XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
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();
if (TAG_INCLUDE.equals(name)) {
final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include);
final int resId = a.getResourceId(R.styleable.Include_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);
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 screen = a.getString(R.styleable.Favorite_screen);
String x = a.getString(R.styleable.Favorite_x);
String y = a.getString(R.styleable.Favorite_y);
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 (LOGD) {
final String title = a.getString(R.styleable.Favorite_title);
final String pkg = a.getString(R.styleable.Favorite_packageName);
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>"),
"", name,
(something == null ? "" : (" \"" + something + "\"")),
container, screen, x, 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 (LOGD) {
final String pkg = ar.getString(R.styleable.Favorite_packageName);
final String uri = ar.getString(R.styleable.Favorite_uri);
Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
folder_item_name, uri != null ? uri : pkg));
}
if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
long id =
addAppShortcut(db, values, ar, packageManager, intent);
if (id >= 0) {
folderItems.add(id);
}
} else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
long id = addUriShortcut(db, values, ar);
if (id >= 0) {
folderItems.add(id);
}
} else {
throw new RuntimeException("Folders can " +
"contain only shortcuts");
}
ar.recycle();
}
// We can only have folders with >= 2 items, so we need to remove the
// folder and clean up if less than 2 items were included, or some
// failed to add, and less than 2 were actually added
if (folderItems.size() < 2 && folderId >= 0) {
// We just delete the folder and any items that made it
deleteId(db, folderId);
if (folderItems.size() > 0) {
deleteId(db, folderItems.get(0));
}
added = false;
}
}
if (added) i++;
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);
}
// Update the max item id after we have loaded the database
if (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(db);
}
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 = 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;
}
} 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 = generateNewItemId();
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.APPWIDGET_PROVIDER, cn.flattenToString());
values.put(Favorites._ID, generateNewItemId());
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 = 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.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;
}
public void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
final ContentResolver resolver = mContext.getContentResolver();
Cursor c = null;
int count = 0;
int curScreen = 0;
try {
c = resolver.query(uri, null, null, null, "title ASC");
} catch (Exception e) {
// Ignore
}
// We already have a favorites database in the old provider
if (c != null) {
try {
if (c.getCount() > 0) {
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);
int i = 0;
int curX = 0;
int curY = 0;
final LauncherAppState app = LauncherAppState.getInstance();
final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
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());
final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
while (c.moveToNext()) {
final int itemType = c.getInt(itemTypeIndex);
if (itemType != Favorites.ITEM_TYPE_APPLICATION
&& itemType != Favorites.ITEM_TYPE_SHORTCUT
&& itemType != Favorites.ITEM_TYPE_FOLDER) {
continue;
}
final int cellX = c.getInt(cellXIndex);
final int cellY = c.getInt(cellYIndex);
final int screen = c.getInt(screenIndex);
int container = c.getInt(containerIndex);
final String intentStr = c.getString(intentIndex);
Launcher.addDumpLog(TAG, "migrating \""
+ c.getString(titleIndex) + "\" ("
+ cellX + "," + cellY + "@"
+ LauncherSettings.Favorites.containerToString(container)
+ "/" + screen
+ "): " + intentStr, true);
if (itemType != Favorites.ITEM_TYPE_FOLDER) {
final Intent intent;
final ComponentName cn;
try {
intent = Intent.parseUri(intentStr, 0);
} catch (URISyntaxException e) {
// bogus intent?
Launcher.addDumpLog(TAG,
"skipping invalid intent uri", true);
continue;
}
cn = intent.getComponent();
if (TextUtils.isEmpty(intentStr)) {
// no intent? no icon
Launcher.addDumpLog(TAG, "skipping empty intent", true);
continue;
} else if (cn != null &&
!LauncherModel.isValidPackageComponent(pm, cn)) {
// component no longer exists.
Launcher.addDumpLog(TAG, "skipping item whose component " +
"no longer exists.", true);
continue;
} else if (container ==
LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// Dedupe icons directly on the workspace
// Canonicalize
// the Play Store sets the package parameter, but Launcher
// does not, so we clear that out to keep them the same
intent.setPackage(null);
final String key = intent.toUri(0);
if (seenIntents.contains(key)) {
Launcher.addDumpLog(TAG, "skipping duplicate", true);
continue;
} else {
seenIntents.add(key);
}
}
}
ContentValues values = new ContentValues(c.getColumnCount());
values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
values.put(LauncherSettings.Favorites.INTENT, intentStr);
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.ITEM_TYPE, itemType);
values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
values.put(LauncherSettings.Favorites.DISPLAY_MODE,
c.getInt(displayModeIndex));
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
hotseat.put(screen, values);
}
if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// In a folder or in the hotseat, preserve position
values.put(LauncherSettings.Favorites.SCREEN, screen);
values.put(LauncherSettings.Favorites.CELLX, cellX);
values.put(LauncherSettings.Favorites.CELLY, cellY);
} else {
// For items contained directly on one of the workspace screen,
// we'll determine their location (screen, x, y) in a second pass.
}
values.put(LauncherSettings.Favorites.CONTAINER, container);
if (itemType != Favorites.ITEM_TYPE_FOLDER) {
shortcuts.add(values);
} else {
folders.add(values);
}
}
// Now that we have all the hotseat icons, let's go through them left-right
// and assign valid locations for them in the new hotseat
final int N = hotseat.size();
for (int idx=0; idx<N; idx++) {
int hotseatX = hotseat.keyAt(idx);
ContentValues values = hotseat.valueAt(idx);
if (hotseatX == grid.hotseatAllAppsRank) {
// let's drop this in the next available hole in the hotseat
while (++hotseatX < hotseatWidth) {
if (hotseat.get(hotseatX) == null) {
// found a spot! move it here
values.put(LauncherSettings.Favorites.SCREEN,
hotseatX);
break;
}
}
}
if (hotseatX >= hotseatWidth) {
// no room for you in the hotseat? it's off to the desktop with you
values.put(LauncherSettings.Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP);
}
}
final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
// Folders first
allItems.addAll(folders);
// Then shortcuts
allItems.addAll(shortcuts);
// Layout all the folders
for (ContentValues values: allItems) {
if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// Hotseat items and folder items have already had their
// location information set. Nothing to be done here.
continue;
}
values.put(LauncherSettings.Favorites.SCREEN, curScreen);
values.put(LauncherSettings.Favorites.CELLX, curX);
values.put(LauncherSettings.Favorites.CELLY, curY);
curX = (curX + 1) % width;
if (curX == 0) {
curY = (curY + 1);
}
// Leave the last row of icons blank on every screen
if (curY == height - 1) {
curScreen = (int) generateNewScreenId();
curY = 0;
}
}
if (allItems.size() > 0) {
db.beginTransaction();
try {
for (ContentValues row: allItems) {
if (row == null) continue;
if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
< 0) {
return;
} else {
count++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
db.beginTransaction();
try {
for (i=0; i<=curScreen; i++) {
final ContentValues values = new ContentValues();
values.put(LauncherSettings.WorkspaceScreens._ID, i);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
< 0) {
return;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} finally {
c.close();
}
}
Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
+ (curScreen+1) + " screens", true);
// ensure that new screens are created to hold these icons
setFlagJustLoadedOldDb();
// Update max IDs; very important since we just grabbed IDs from another database
mMaxItemId = initializeMaxItemId(db);
mMaxScreenId = initializeMaxScreenId(db);
if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
}
}
/**
* 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);
}
}
}
}