LauncherProvider.java revision a33f11e20a62bf49596c0dcd8741e3944a57b2f9
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3;
18
19import android.app.SearchManager;
20import android.appwidget.AppWidgetHost;
21import android.appwidget.AppWidgetManager;
22import android.appwidget.AppWidgetProviderInfo;
23import android.content.ComponentName;
24import android.content.ContentProvider;
25import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
27import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.ContentValues;
30import android.content.Context;
31import android.content.Intent;
32import android.content.OperationApplicationException;
33import android.content.SharedPreferences;
34import android.content.res.Resources;
35import android.database.Cursor;
36import android.database.SQLException;
37import android.database.sqlite.SQLiteDatabase;
38import android.database.sqlite.SQLiteOpenHelper;
39import android.database.sqlite.SQLiteQueryBuilder;
40import android.database.sqlite.SQLiteStatement;
41import android.graphics.Bitmap;
42import android.graphics.BitmapFactory;
43import android.net.Uri;
44import android.provider.Settings;
45import android.text.TextUtils;
46import android.util.Log;
47import android.util.SparseArray;
48
49import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
50import com.android.launcher3.LauncherSettings.Favorites;
51import com.android.launcher3.compat.UserHandleCompat;
52import com.android.launcher3.compat.UserManagerCompat;
53import com.android.launcher3.config.ProviderConfig;
54
55import java.io.File;
56import java.net.URISyntaxException;
57import java.util.ArrayList;
58import java.util.Collections;
59import java.util.HashSet;
60import java.util.List;
61
62public class LauncherProvider extends ContentProvider {
63    private static final String TAG = "Launcher.LauncherProvider";
64    private static final boolean LOGD = false;
65
66    private static final int DATABASE_VERSION = 20;
67
68    static final String OLD_AUTHORITY = "com.android.launcher2.settings";
69    static final String AUTHORITY = ProviderConfig.AUTHORITY;
70
71    // Should we attempt to load anything from the com.android.launcher2 provider?
72    static final boolean IMPORT_LAUNCHER2_DATABASE = false;
73
74    static final String TABLE_FAVORITES = "favorites";
75    static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
76    static final String PARAMETER_NOTIFY = "notify";
77    static final String UPGRADED_FROM_OLD_DATABASE =
78            "UPGRADED_FROM_OLD_DATABASE";
79    static final String EMPTY_DATABASE_CREATED =
80            "EMPTY_DATABASE_CREATED";
81
82    private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
83
84    private LauncherProviderChangeListener mListener;
85
86    /**
87     * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
88     * {@link AppWidgetHost#deleteHost()} is called during database creation.
89     * Use this to recall {@link AppWidgetHost#startListening()} if needed.
90     */
91    static final Uri CONTENT_APPWIDGET_RESET_URI =
92            Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
93
94    private DatabaseHelper mOpenHelper;
95    private static boolean sJustLoadedFromOldDb;
96
97    @Override
98    public boolean onCreate() {
99        final Context context = getContext();
100        mOpenHelper = new DatabaseHelper(context);
101        LauncherAppState.setLauncherProvider(this);
102        return true;
103    }
104
105    public boolean wasNewDbCreated() {
106        return mOpenHelper.wasNewDbCreated();
107    }
108
109    public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
110        mListener = listener;
111    }
112
113    @Override
114    public String getType(Uri uri) {
115        SqlArguments args = new SqlArguments(uri, null, null);
116        if (TextUtils.isEmpty(args.where)) {
117            return "vnd.android.cursor.dir/" + args.table;
118        } else {
119            return "vnd.android.cursor.item/" + args.table;
120        }
121    }
122
123    @Override
124    public Cursor query(Uri uri, String[] projection, String selection,
125            String[] selectionArgs, String sortOrder) {
126
127        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
128        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
129        qb.setTables(args.table);
130
131        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
132        Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
133        result.setNotificationUri(getContext().getContentResolver(), uri);
134
135        return result;
136    }
137
138    private static long dbInsertAndCheck(DatabaseHelper helper,
139            SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
140        if (values == null) {
141            throw new RuntimeException("Error: attempting to insert null values");
142        }
143        if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
144            throw new RuntimeException("Error: attempting to add item without specifying an id");
145        }
146        helper.checkId(table, values);
147        return db.insert(table, nullColumnHack, values);
148    }
149
150    @Override
151    public Uri insert(Uri uri, ContentValues initialValues) {
152        SqlArguments args = new SqlArguments(uri);
153
154        // In very limited cases, we support system|signature permission apps to add to the db
155        String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
156        if (externalAdd != null && "true".equals(externalAdd)) {
157            if (!mOpenHelper.initializeExternalAdd(initialValues)) {
158                return null;
159            }
160        }
161
162        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
163        addModifiedTime(initialValues);
164        final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
165        if (rowId <= 0) return null;
166
167        uri = ContentUris.withAppendedId(uri, rowId);
168        sendNotify(uri);
169
170        return uri;
171    }
172
173
174    @Override
175    public int bulkInsert(Uri uri, ContentValues[] values) {
176        SqlArguments args = new SqlArguments(uri);
177
178        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
179        db.beginTransaction();
180        try {
181            int numValues = values.length;
182            for (int i = 0; i < numValues; i++) {
183                addModifiedTime(values[i]);
184                if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
185                    return 0;
186                }
187            }
188            db.setTransactionSuccessful();
189        } finally {
190            db.endTransaction();
191        }
192
193        sendNotify(uri);
194        return values.length;
195    }
196
197    @Override
198    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
199            throws OperationApplicationException {
200        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
201        db.beginTransaction();
202        try {
203            ContentProviderResult[] result =  super.applyBatch(operations);
204            db.setTransactionSuccessful();
205            return result;
206        } finally {
207            db.endTransaction();
208        }
209    }
210
211    @Override
212    public int delete(Uri uri, String selection, String[] selectionArgs) {
213        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
214
215        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
216        int count = db.delete(args.table, args.where, args.args);
217        if (count > 0) sendNotify(uri);
218
219        return count;
220    }
221
222    @Override
223    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
224        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
225
226        addModifiedTime(values);
227        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
228        int count = db.update(args.table, values, args.where, args.args);
229        if (count > 0) sendNotify(uri);
230
231        return count;
232    }
233
234    private void sendNotify(Uri uri) {
235        String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
236        if (notify == null || "true".equals(notify)) {
237            getContext().getContentResolver().notifyChange(uri, null);
238        }
239
240        // always notify the backup agent
241        LauncherBackupAgentHelper.dataChanged(getContext());
242        if (mListener != null) {
243            mListener.onLauncherProviderChange();
244        }
245    }
246
247    private void addModifiedTime(ContentValues values) {
248        values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
249    }
250
251    public long generateNewItemId() {
252        return mOpenHelper.generateNewItemId();
253    }
254
255    public void updateMaxItemId(long id) {
256        mOpenHelper.updateMaxItemId(id);
257    }
258
259    public long generateNewScreenId() {
260        return mOpenHelper.generateNewScreenId();
261    }
262
263    // This is only required one time while loading the workspace during the
264    // upgrade path, and should never be called from anywhere else.
265    public void updateMaxScreenId(long maxScreenId) {
266        mOpenHelper.updateMaxScreenId(maxScreenId);
267    }
268
269    /**
270     * @param Should we load the old db for upgrade? first run only.
271     */
272    synchronized public boolean justLoadedOldDb() {
273        String spKey = LauncherAppState.getSharedPreferencesKey();
274        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
275
276        boolean loadedOldDb = false || sJustLoadedFromOldDb;
277
278        sJustLoadedFromOldDb = false;
279        if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
280
281            SharedPreferences.Editor editor = sp.edit();
282            editor.remove(UPGRADED_FROM_OLD_DATABASE);
283            editor.commit();
284            loadedOldDb = true;
285        }
286        return loadedOldDb;
287    }
288
289    /**
290     * Clears all the data for a fresh start.
291     */
292    synchronized public void createEmptyDB() {
293        mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
294    }
295
296    /**
297     * Loads the default workspace based on the following priority scheme:
298     *   1) From a package provided by play store
299     *   2) From a partner configuration APK, already in the system image
300     *   3) The default configuration for the particular device
301     */
302    synchronized public void loadDefaultFavoritesIfNecessary() {
303        String spKey = LauncherAppState.getSharedPreferencesKey();
304        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
305
306        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
307            Log.d(TAG, "loading default workspace");
308
309            AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(),
310                    mOpenHelper.mAppWidgetHost, mOpenHelper);
311
312            if (loader == null) {
313                final Partner partner = Partner.get(getContext().getPackageManager());
314                if (partner != null && partner.hasDefaultLayout()) {
315                    final Resources partnerRes = partner.getResources();
316                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
317                            "xml", partner.getPackageName());
318                    if (workspaceResId != 0) {
319                        loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
320                                mOpenHelper, partnerRes, workspaceResId);
321                    }
322                }
323            }
324
325            if (loader == null) {
326                loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
327                        mOpenHelper, getContext().getResources(), getDefaultWorkspaceResourceId());
328            }
329
330            // Populate favorites table with initial favorites
331            SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED);
332            mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader);
333            editor.commit();
334        }
335    }
336
337    public void migrateLauncher2Shortcuts() {
338        mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
339                Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
340    }
341
342    private static int getDefaultWorkspaceResourceId() {
343        LauncherAppState app = LauncherAppState.getInstance();
344        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
345        return grid.defaultLayoutId;
346    }
347
348    private static interface ContentValuesCallback {
349        public void onRow(ContentValues values);
350    }
351
352    private static boolean shouldImportLauncher2Database(Context context) {
353        boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
354
355        // We don't import the old databse for tablets, as the grid size has changed.
356        return !isTablet && IMPORT_LAUNCHER2_DATABASE;
357    }
358
359    public void deleteDatabase() {
360        // Are you sure? (y/n)
361        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
362        final File dbFile = new File(db.getPath());
363        mOpenHelper.close();
364        if (dbFile.exists()) {
365            SQLiteDatabase.deleteDatabase(dbFile);
366        }
367        mOpenHelper = new DatabaseHelper(getContext());
368    }
369
370    private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
371        private final Context mContext;
372        private final AppWidgetHost mAppWidgetHost;
373        private long mMaxItemId = -1;
374        private long mMaxScreenId = -1;
375
376        private boolean mNewDbCreated = false;
377
378        DatabaseHelper(Context context) {
379            super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION);
380            mContext = context;
381            mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
382
383            // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
384            // the DB here
385            if (mMaxItemId == -1) {
386                mMaxItemId = initializeMaxItemId(getWritableDatabase());
387            }
388            if (mMaxScreenId == -1) {
389                mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
390            }
391        }
392
393        public boolean wasNewDbCreated() {
394            return mNewDbCreated;
395        }
396
397        /**
398         * Send notification that we've deleted the {@link AppWidgetHost},
399         * probably as part of the initial database creation. The receiver may
400         * want to re-call {@link AppWidgetHost#startListening()} to ensure
401         * callbacks are correctly set.
402         */
403        private void sendAppWidgetResetNotify() {
404            final ContentResolver resolver = mContext.getContentResolver();
405            resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
406        }
407
408        @Override
409        public void onCreate(SQLiteDatabase db) {
410            if (LOGD) Log.d(TAG, "creating new launcher database");
411
412            mMaxItemId = 1;
413            mMaxScreenId = 0;
414            mNewDbCreated = true;
415
416            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
417            long userSerialNumber = userManager.getSerialNumberForUser(
418                    UserHandleCompat.myUserHandle());
419
420            db.execSQL("CREATE TABLE favorites (" +
421                    "_id INTEGER PRIMARY KEY," +
422                    "title TEXT," +
423                    "intent TEXT," +
424                    "container INTEGER," +
425                    "screen INTEGER," +
426                    "cellX INTEGER," +
427                    "cellY INTEGER," +
428                    "spanX INTEGER," +
429                    "spanY INTEGER," +
430                    "itemType INTEGER," +
431                    "appWidgetId INTEGER NOT NULL DEFAULT -1," +
432                    "isShortcut INTEGER," +
433                    "iconType INTEGER," +
434                    "iconPackage TEXT," +
435                    "iconResource TEXT," +
436                    "icon BLOB," +
437                    "uri TEXT," +
438                    "displayMode INTEGER," +
439                    "appWidgetProvider TEXT," +
440                    "modified INTEGER NOT NULL DEFAULT 0," +
441                    "restored INTEGER NOT NULL DEFAULT 0," +
442                    "profileId INTEGER DEFAULT " + userSerialNumber +
443                    ");");
444            addWorkspacesTable(db);
445
446            // Database was just created, so wipe any previous widgets
447            if (mAppWidgetHost != null) {
448                mAppWidgetHost.deleteHost();
449                sendAppWidgetResetNotify();
450            }
451
452            if (shouldImportLauncher2Database(mContext)) {
453                // Try converting the old database
454                ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
455                    public void onRow(ContentValues values) {
456                        int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
457                        if (container == Favorites.CONTAINER_DESKTOP) {
458                            int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
459                            screen = (int) upgradeLauncherDb_permuteScreens(screen);
460                            values.put(LauncherSettings.Favorites.SCREEN, screen);
461                        }
462                    }
463                };
464                Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
465                        "/old_favorites?notify=true");
466                if (!convertDatabase(db, uri, permuteScreensCb, true)) {
467                    // Try and upgrade from the Launcher2 db
468                    uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri));
469                    if (!convertDatabase(db, uri, permuteScreensCb, false)) {
470                        // If we fail, then set a flag to load the default workspace
471                        setFlagEmptyDbCreated();
472                        return;
473                    }
474                }
475                // Right now, in non-default workspace cases, we want to run the final
476                // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
477                // set that flag too.
478                setFlagJustLoadedOldDb();
479            } else {
480                // Fresh and clean launcher DB.
481                mMaxItemId = initializeMaxItemId(db);
482                setFlagEmptyDbCreated();
483            }
484        }
485
486        private void addWorkspacesTable(SQLiteDatabase db) {
487            db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
488                    LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
489                    LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
490                    LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
491                    ");");
492        }
493
494        private void removeOrphanedItems(SQLiteDatabase db) {
495            // Delete items directly on the workspace who's screen id doesn't exist
496            //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
497            //   AND container = -100"
498            String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
499                    " WHERE " +
500                    LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
501                    LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
502                    " AND " +
503                    LauncherSettings.Favorites.CONTAINER + " = " +
504                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
505            db.execSQL(removeOrphanedDesktopItems);
506
507            // Delete items contained in folders which no longer exist (after above statement)
508            //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
509            //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
510            String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
511                    " WHERE " +
512                    LauncherSettings.Favorites.CONTAINER + " <> " +
513                    LauncherSettings.Favorites.CONTAINER_DESKTOP +
514                    " AND "
515                    + LauncherSettings.Favorites.CONTAINER + " <> " +
516                    LauncherSettings.Favorites.CONTAINER_HOTSEAT +
517                    " AND "
518                    + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
519                    LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
520                    " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
521                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
522            db.execSQL(removeOrphanedFolderItems);
523        }
524
525        private void setFlagJustLoadedOldDb() {
526            String spKey = LauncherAppState.getSharedPreferencesKey();
527            SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
528            SharedPreferences.Editor editor = sp.edit();
529            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
530            editor.putBoolean(EMPTY_DATABASE_CREATED, false);
531            editor.commit();
532        }
533
534        private void setFlagEmptyDbCreated() {
535            String spKey = LauncherAppState.getSharedPreferencesKey();
536            SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
537            SharedPreferences.Editor editor = sp.edit();
538            editor.putBoolean(EMPTY_DATABASE_CREATED, true);
539            editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
540            editor.commit();
541        }
542
543        // We rearrange the screens from the old launcher
544        // 12345 -> 34512
545        private long upgradeLauncherDb_permuteScreens(long screen) {
546            if (screen >= 2) {
547                return screen - 2;
548            } else {
549                return screen + 3;
550            }
551        }
552
553        private boolean convertDatabase(SQLiteDatabase db, Uri uri,
554                                        ContentValuesCallback cb, boolean deleteRows) {
555            if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
556            boolean converted = false;
557
558            final ContentResolver resolver = mContext.getContentResolver();
559            Cursor cursor = null;
560
561            try {
562                cursor = resolver.query(uri, null, null, null, null);
563            } catch (Exception e) {
564                // Ignore
565            }
566
567            // We already have a favorites database in the old provider
568            if (cursor != null) {
569                try {
570                     if (cursor.getCount() > 0) {
571                        converted = copyFromCursor(db, cursor, cb) > 0;
572                        if (converted && deleteRows) {
573                            resolver.delete(uri, null, null);
574                        }
575                    }
576                } finally {
577                    cursor.close();
578                }
579            }
580
581            if (converted) {
582                // Convert widgets from this import into widgets
583                if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
584                convertWidgets(db);
585
586                // Update max item id
587                mMaxItemId = initializeMaxItemId(db);
588                if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
589            }
590
591            return converted;
592        }
593
594        private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
595            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
596            final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
597            final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
598            final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
599            final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
600            final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
601            final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
602            final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
603            final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
604            final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
605            final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
606            final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
607            final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
608            final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
609
610            ContentValues[] rows = new ContentValues[c.getCount()];
611            int i = 0;
612            while (c.moveToNext()) {
613                ContentValues values = new ContentValues(c.getColumnCount());
614                values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
615                values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
616                values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
617                values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
618                values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
619                values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
620                values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
621                values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
622                values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
623                values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
624                values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
625                values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
626                values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
627                values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
628                values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
629                if (cb != null) {
630                    cb.onRow(values);
631                }
632                rows[i++] = values;
633            }
634
635            int total = 0;
636            if (i > 0) {
637                db.beginTransaction();
638                try {
639                    int numValues = rows.length;
640                    for (i = 0; i < numValues; i++) {
641                        if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
642                            return 0;
643                        } else {
644                            total++;
645                        }
646                    }
647                    db.setTransactionSuccessful();
648                } finally {
649                    db.endTransaction();
650                }
651            }
652
653            return total;
654        }
655
656        @Override
657        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
658            if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
659
660            int version = oldVersion;
661            if (version < 3) {
662                // upgrade 1,2 -> 3 added appWidgetId column
663                db.beginTransaction();
664                try {
665                    // Insert new column for holding appWidgetIds
666                    db.execSQL("ALTER TABLE favorites " +
667                        "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
668                    db.setTransactionSuccessful();
669                    version = 3;
670                } catch (SQLException ex) {
671                    // Old version remains, which means we wipe old data
672                    Log.e(TAG, ex.getMessage(), ex);
673                } finally {
674                    db.endTransaction();
675                }
676
677                // Convert existing widgets only if table upgrade was successful
678                if (version == 3) {
679                    convertWidgets(db);
680                }
681            }
682
683            if (version < 4) {
684                version = 4;
685            }
686
687            // Where's version 5?
688            // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
689            // - Passion shipped on 2.1 with version 6 of launcher3
690            // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
691            //   but version 5 on there was the updateContactsShortcuts change
692            //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
693            // The updateContactsShortcuts change is idempotent, so running it twice
694            // is okay so we'll do that when upgrading the devices that shipped with it.
695            if (version < 6) {
696                // We went from 3 to 5 screens. Move everything 1 to the right
697                db.beginTransaction();
698                try {
699                    db.execSQL("UPDATE favorites SET screen=(screen + 1);");
700                    db.setTransactionSuccessful();
701                } catch (SQLException ex) {
702                    // Old version remains, which means we wipe old data
703                    Log.e(TAG, ex.getMessage(), ex);
704                } finally {
705                    db.endTransaction();
706                }
707
708               // We added the fast track.
709                if (updateContactsShortcuts(db)) {
710                    version = 6;
711                }
712            }
713
714            if (version < 7) {
715                // Version 7 gets rid of the special search widget.
716                convertWidgets(db);
717                version = 7;
718            }
719
720            if (version < 8) {
721                // Version 8 (froyo) has the icons all normalized.  This should
722                // already be the case in practice, but we now rely on it and don't
723                // resample the images each time.
724                normalizeIcons(db);
725                version = 8;
726            }
727
728            if (version < 9) {
729                // The max id is not yet set at this point (onUpgrade is triggered in the ctor
730                // before it gets a change to get set, so we need to read it here when we use it)
731                if (mMaxItemId == -1) {
732                    mMaxItemId = initializeMaxItemId(db);
733                }
734
735                // Add default hotseat icons
736                loadFavorites(db, new DefaultLayoutParser(mContext, mAppWidgetHost, this,
737                        mContext.getResources(), R.xml.update_workspace));
738                version = 9;
739            }
740
741            // We bumped the version three time during JB, once to update the launch flags, once to
742            // update the override for the default launch animation and once to set the mimetype
743            // to improve startup performance
744            if (version < 12) {
745                // Contact shortcuts need a different set of flags to be launched now
746                // The updateContactsShortcuts change is idempotent, so we can keep using it like
747                // back in the Donut days
748                updateContactsShortcuts(db);
749                version = 12;
750            }
751
752            if (version < 13) {
753                // With the new shrink-wrapped and re-orderable workspaces, it makes sense
754                // to persist workspace screens and their relative order.
755                mMaxScreenId = 0;
756
757                // This will never happen in the wild, but when we switch to using workspace
758                // screen ids, redo the import from old launcher.
759                sJustLoadedFromOldDb = true;
760
761                addWorkspacesTable(db);
762                version = 13;
763            }
764
765            if (version < 14) {
766                db.beginTransaction();
767                try {
768                    // Insert new column for holding widget provider name
769                    db.execSQL("ALTER TABLE favorites " +
770                            "ADD COLUMN appWidgetProvider TEXT;");
771                    db.setTransactionSuccessful();
772                    version = 14;
773                } catch (SQLException ex) {
774                    // Old version remains, which means we wipe old data
775                    Log.e(TAG, ex.getMessage(), ex);
776                } finally {
777                    db.endTransaction();
778                }
779            }
780
781            if (version < 15) {
782                db.beginTransaction();
783                try {
784                    // Insert new column for holding update timestamp
785                    db.execSQL("ALTER TABLE favorites " +
786                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
787                    db.execSQL("ALTER TABLE workspaceScreens " +
788                            "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
789                    db.setTransactionSuccessful();
790                    version = 15;
791                } catch (SQLException ex) {
792                    // Old version remains, which means we wipe old data
793                    Log.e(TAG, ex.getMessage(), ex);
794                } finally {
795                    db.endTransaction();
796                }
797            }
798
799
800            if (version < 16) {
801                db.beginTransaction();
802                try {
803                    // Insert new column for holding restore status
804                    db.execSQL("ALTER TABLE favorites " +
805                            "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
806                    db.setTransactionSuccessful();
807                    version = 16;
808                } catch (SQLException ex) {
809                    // Old version remains, which means we wipe old data
810                    Log.e(TAG, ex.getMessage(), ex);
811                } finally {
812                    db.endTransaction();
813                }
814            }
815
816            if (version < 17) {
817                // We use the db version upgrade here to identify users who may not have seen
818                // clings yet (because they weren't available), but for whom the clings are now
819                // available (tablet users). Because one of the possible cling flows (migration)
820                // is very destructive (wipes out workspaces), we want to prevent this from showing
821                // until clear data. We do so by marking that the clings have been shown.
822                LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
823                version = 17;
824            }
825
826            if (version < 18) {
827                // No-op
828                version = 18;
829            }
830
831            if (version < 19) {
832                // Due to a data loss bug, some users may have items associated with screen ids
833                // which no longer exist. Since this can cause other problems, and since the user
834                // will never see these items anyway, we use database upgrade as an opportunity to
835                // clean things up.
836                removeOrphanedItems(db);
837                version = 19;
838            }
839
840            if (version < 20) {
841                // Add userId column
842                if (addProfileColumn(db)) {
843                    version = 20;
844                }
845                // else old version remains, which means we wipe old data
846            }
847
848            if (version != DATABASE_VERSION) {
849                Log.w(TAG, "Destroying all old data.");
850                db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
851                db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
852
853                onCreate(db);
854            }
855        }
856
857        @Override
858        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
859            // This shouldn't happen -- throw our hands up in the air and start over.
860            Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
861                    ". Wiping databse.");
862            createEmptyDB(db);
863        }
864
865
866        /**
867         * Clears all the data for a fresh start.
868         */
869        public void createEmptyDB(SQLiteDatabase db) {
870            db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
871            db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
872            onCreate(db);
873        }
874
875        private boolean addProfileColumn(SQLiteDatabase db) {
876            db.beginTransaction();
877            try {
878                UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
879                // Default to the serial number of this user, for older
880                // shortcuts.
881                long userSerialNumber = userManager.getSerialNumberForUser(
882                        UserHandleCompat.myUserHandle());
883                // Insert new column for holding user serial number
884                db.execSQL("ALTER TABLE favorites " +
885                        "ADD COLUMN profileId INTEGER DEFAULT "
886                                        + userSerialNumber + ";");
887                db.setTransactionSuccessful();
888            } catch (SQLException ex) {
889                // Old version remains, which means we wipe old data
890                Log.e(TAG, ex.getMessage(), ex);
891                return false;
892            } finally {
893                db.endTransaction();
894            }
895            return true;
896        }
897
898        private boolean updateContactsShortcuts(SQLiteDatabase db) {
899            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
900                    new int[] { Favorites.ITEM_TYPE_SHORTCUT });
901
902            Cursor c = null;
903            final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
904            db.beginTransaction();
905            try {
906                // Select and iterate through each matching widget
907                c = db.query(TABLE_FAVORITES,
908                        new String[] { Favorites._ID, Favorites.INTENT },
909                        selectWhere, null, null, null, null);
910                if (c == null) return false;
911
912                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
913
914                final int idIndex = c.getColumnIndex(Favorites._ID);
915                final int intentIndex = c.getColumnIndex(Favorites.INTENT);
916
917                while (c.moveToNext()) {
918                    long favoriteId = c.getLong(idIndex);
919                    final String intentUri = c.getString(intentIndex);
920                    if (intentUri != null) {
921                        try {
922                            final Intent intent = Intent.parseUri(intentUri, 0);
923                            android.util.Log.d("Home", intent.toString());
924                            final Uri uri = intent.getData();
925                            if (uri != null) {
926                                final String data = uri.toString();
927                                if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
928                                        actionQuickContact.equals(intent.getAction())) &&
929                                        (data.startsWith("content://contacts/people/") ||
930                                        data.startsWith("content://com.android.contacts/" +
931                                                "contacts/lookup/"))) {
932
933                                    final Intent newIntent = new Intent(actionQuickContact);
934                                    // When starting from the launcher, start in a new, cleared task
935                                    // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
936                                    // clear the whole thing preemptively here since
937                                    // QuickContactActivity will finish itself when launching other
938                                    // detail activities.
939                                    newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
940                                            Intent.FLAG_ACTIVITY_CLEAR_TASK);
941                                    newIntent.putExtra(
942                                            Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
943                                    newIntent.setData(uri);
944                                    // Determine the type and also put that in the shortcut
945                                    // (that can speed up launch a bit)
946                                    newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
947
948                                    final ContentValues values = new ContentValues();
949                                    values.put(LauncherSettings.Favorites.INTENT,
950                                            newIntent.toUri(0));
951
952                                    String updateWhere = Favorites._ID + "=" + favoriteId;
953                                    db.update(TABLE_FAVORITES, values, updateWhere, null);
954                                }
955                            }
956                        } catch (RuntimeException ex) {
957                            Log.e(TAG, "Problem upgrading shortcut", ex);
958                        } catch (URISyntaxException e) {
959                            Log.e(TAG, "Problem upgrading shortcut", e);
960                        }
961                    }
962                }
963
964                db.setTransactionSuccessful();
965            } catch (SQLException ex) {
966                Log.w(TAG, "Problem while upgrading contacts", ex);
967                return false;
968            } finally {
969                db.endTransaction();
970                if (c != null) {
971                    c.close();
972                }
973            }
974
975            return true;
976        }
977
978        private void normalizeIcons(SQLiteDatabase db) {
979            Log.d(TAG, "normalizing icons");
980
981            db.beginTransaction();
982            Cursor c = null;
983            SQLiteStatement update = null;
984            try {
985                boolean logged = false;
986                update = db.compileStatement("UPDATE favorites "
987                        + "SET icon=? WHERE _id=?");
988
989                c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
990                        Favorites.ICON_TYPE_BITMAP, null);
991
992                final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
993                final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
994
995                while (c.moveToNext()) {
996                    long id = c.getLong(idIndex);
997                    byte[] data = c.getBlob(iconIndex);
998                    try {
999                        Bitmap bitmap = Utilities.createIconBitmap(
1000                                BitmapFactory.decodeByteArray(data, 0, data.length),
1001                                mContext);
1002                        if (bitmap != null) {
1003                            update.bindLong(1, id);
1004                            data = ItemInfo.flattenBitmap(bitmap);
1005                            if (data != null) {
1006                                update.bindBlob(2, data);
1007                                update.execute();
1008                            }
1009                            bitmap.recycle();
1010                        }
1011                    } catch (Exception e) {
1012                        if (!logged) {
1013                            Log.e(TAG, "Failed normalizing icon " + id, e);
1014                        } else {
1015                            Log.e(TAG, "Also failed normalizing icon " + id);
1016                        }
1017                        logged = true;
1018                    }
1019                }
1020                db.setTransactionSuccessful();
1021            } catch (SQLException ex) {
1022                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
1023            } finally {
1024                db.endTransaction();
1025                if (update != null) {
1026                    update.close();
1027                }
1028                if (c != null) {
1029                    c.close();
1030                }
1031            }
1032        }
1033
1034        // Generates a new ID to use for an object in your database. This method should be only
1035        // called from the main UI thread. As an exception, we do call it when we call the
1036        // constructor from the worker thread; however, this doesn't extend until after the
1037        // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1038        // after that point
1039        @Override
1040        public long generateNewItemId() {
1041            if (mMaxItemId < 0) {
1042                throw new RuntimeException("Error: max item id was not initialized");
1043            }
1044            mMaxItemId += 1;
1045            return mMaxItemId;
1046        }
1047
1048        @Override
1049        public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
1050            return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
1051        }
1052
1053        public void updateMaxItemId(long id) {
1054            mMaxItemId = id + 1;
1055        }
1056
1057        public void checkId(String table, ContentValues values) {
1058            long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
1059            if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
1060                mMaxScreenId = Math.max(id, mMaxScreenId);
1061            }  else {
1062                mMaxItemId = Math.max(id, mMaxItemId);
1063            }
1064        }
1065
1066        private long initializeMaxItemId(SQLiteDatabase db) {
1067            Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
1068
1069            // get the result
1070            final int maxIdIndex = 0;
1071            long id = -1;
1072            if (c != null && c.moveToNext()) {
1073                id = c.getLong(maxIdIndex);
1074            }
1075            if (c != null) {
1076                c.close();
1077            }
1078
1079            if (id == -1) {
1080                throw new RuntimeException("Error: could not query max item id");
1081            }
1082
1083            return id;
1084        }
1085
1086        // Generates a new ID to use for an workspace screen in your database. This method
1087        // should be only called from the main UI thread. As an exception, we do call it when we
1088        // call the constructor from the worker thread; however, this doesn't extend until after the
1089        // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1090        // after that point
1091        public long generateNewScreenId() {
1092            if (mMaxScreenId < 0) {
1093                throw new RuntimeException("Error: max screen id was not initialized");
1094            }
1095            mMaxScreenId += 1;
1096            // Log to disk
1097            Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
1098            return mMaxScreenId;
1099        }
1100
1101        public void updateMaxScreenId(long maxScreenId) {
1102            // Log to disk
1103            Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
1104            mMaxScreenId = maxScreenId;
1105        }
1106
1107        private long initializeMaxScreenId(SQLiteDatabase db) {
1108            Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1109
1110            // get the result
1111            final int maxIdIndex = 0;
1112            long id = -1;
1113            if (c != null && c.moveToNext()) {
1114                id = c.getLong(maxIdIndex);
1115            }
1116            if (c != null) {
1117                c.close();
1118            }
1119
1120            if (id == -1) {
1121                throw new RuntimeException("Error: could not query max screen id");
1122            }
1123
1124            // Log to disk
1125            Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
1126            return id;
1127        }
1128
1129        /**
1130         * Upgrade existing clock and photo frame widgets into their new widget
1131         * equivalents.
1132         */
1133        private void convertWidgets(SQLiteDatabase db) {
1134            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1135            final int[] bindSources = new int[] {
1136                    Favorites.ITEM_TYPE_WIDGET_CLOCK,
1137                    Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
1138                    Favorites.ITEM_TYPE_WIDGET_SEARCH,
1139            };
1140
1141            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
1142
1143            Cursor c = null;
1144
1145            db.beginTransaction();
1146            try {
1147                // Select and iterate through each matching widget
1148                c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
1149                        selectWhere, null, null, null, null);
1150
1151                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
1152
1153                final ContentValues values = new ContentValues();
1154                while (c != null && c.moveToNext()) {
1155                    long favoriteId = c.getLong(0);
1156                    int favoriteType = c.getInt(1);
1157
1158                    // Allocate and update database with new appWidgetId
1159                    try {
1160                        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
1161
1162                        if (LOGD) {
1163                            Log.d(TAG, "allocated appWidgetId=" + appWidgetId
1164                                    + " for favoriteId=" + favoriteId);
1165                        }
1166                        values.clear();
1167                        values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
1168                        values.put(Favorites.APPWIDGET_ID, appWidgetId);
1169
1170                        // Original widgets might not have valid spans when upgrading
1171                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
1172                            values.put(LauncherSettings.Favorites.SPANX, 4);
1173                            values.put(LauncherSettings.Favorites.SPANY, 1);
1174                        } else {
1175                            values.put(LauncherSettings.Favorites.SPANX, 2);
1176                            values.put(LauncherSettings.Favorites.SPANY, 2);
1177                        }
1178
1179                        String updateWhere = Favorites._ID + "=" + favoriteId;
1180                        db.update(TABLE_FAVORITES, values, updateWhere, null);
1181
1182                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
1183                            // TODO: check return value
1184                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1185                                    new ComponentName("com.android.alarmclock",
1186                                    "com.android.alarmclock.AnalogAppWidgetProvider"));
1187                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
1188                            // TODO: check return value
1189                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1190                                    new ComponentName("com.android.camera",
1191                                    "com.android.camera.PhotoAppWidgetProvider"));
1192                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
1193                            // TODO: check return value
1194                            appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1195                                    getSearchWidgetProvider());
1196                        }
1197                    } catch (RuntimeException ex) {
1198                        Log.e(TAG, "Problem allocating appWidgetId", ex);
1199                    }
1200                }
1201
1202                db.setTransactionSuccessful();
1203            } catch (SQLException ex) {
1204                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
1205            } finally {
1206                db.endTransaction();
1207                if (c != null) {
1208                    c.close();
1209                }
1210            }
1211
1212            // Update max item id
1213            mMaxItemId = initializeMaxItemId(db);
1214            if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
1215        }
1216
1217        private boolean initializeExternalAdd(ContentValues values) {
1218            // 1. Ensure that externally added items have a valid item id
1219            long id = generateNewItemId();
1220            values.put(LauncherSettings.Favorites._ID, id);
1221
1222            // 2. In the case of an app widget, and if no app widget id is specified, we
1223            // attempt allocate and bind the widget.
1224            Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
1225            if (itemType != null &&
1226                    itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
1227                    !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
1228
1229                final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1230                ComponentName cn = ComponentName.unflattenFromString(
1231                        values.getAsString(Favorites.APPWIDGET_PROVIDER));
1232
1233                if (cn != null) {
1234                    try {
1235                        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
1236                        values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
1237                        if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
1238                            return false;
1239                        }
1240                    } catch (RuntimeException e) {
1241                        Log.e(TAG, "Failed to initialize external widget", e);
1242                        return false;
1243                    }
1244                } else {
1245                    return false;
1246                }
1247            }
1248
1249            // Add screen id if not present
1250            long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
1251            if (!addScreenIdIfNecessary(screenId)) {
1252                return false;
1253            }
1254            return true;
1255        }
1256
1257        // Returns true of screen id exists, or if successfully added
1258        private boolean addScreenIdIfNecessary(long screenId) {
1259            if (!hasScreenId(screenId)) {
1260                int rank = getMaxScreenRank() + 1;
1261
1262                ContentValues v = new ContentValues();
1263                v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1264                v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1265                if (dbInsertAndCheck(this, getWritableDatabase(),
1266                        TABLE_WORKSPACE_SCREENS, null, v) < 0) {
1267                    return false;
1268                }
1269            }
1270            return true;
1271        }
1272
1273        private boolean hasScreenId(long screenId) {
1274            SQLiteDatabase db = getWritableDatabase();
1275            Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1276                    + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1277            if (c != null) {
1278                int count = c.getCount();
1279                c.close();
1280                return count > 0;
1281            } else {
1282                return false;
1283            }
1284        }
1285
1286        private int getMaxScreenRank() {
1287            SQLiteDatabase db = getWritableDatabase();
1288            Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1289                    + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1290
1291            // get the result
1292            final int maxRankIndex = 0;
1293            int rank = -1;
1294            if (c != null && c.moveToNext()) {
1295                rank = c.getInt(maxRankIndex);
1296            }
1297            if (c != null) {
1298                c.close();
1299            }
1300
1301            return rank;
1302        }
1303
1304        private int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
1305            ArrayList<Long> screenIds = new ArrayList<Long>();
1306            // TODO: Use multiple loaders with fall-back and transaction.
1307            int count = loader.loadLayout(db, screenIds);
1308
1309            // Add the screens specified by the items above
1310            Collections.sort(screenIds);
1311            int rank = 0;
1312            ContentValues values = new ContentValues();
1313            for (Long id : screenIds) {
1314                values.clear();
1315                values.put(LauncherSettings.WorkspaceScreens._ID, id);
1316                values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1317                if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1318                    throw new RuntimeException("Failed initialize screen table"
1319                            + "from default layout");
1320                }
1321                rank++;
1322            }
1323
1324            // Ensure that the max ids are initialized
1325            mMaxItemId = initializeMaxItemId(db);
1326            mMaxScreenId = initializeMaxScreenId(db);
1327
1328            return count;
1329        }
1330
1331        private ComponentName getSearchWidgetProvider() {
1332            SearchManager searchManager =
1333                    (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1334            ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1335            if (searchComponent == null) return null;
1336            return getProviderInPackage(searchComponent.getPackageName());
1337        }
1338
1339        /**
1340         * Gets an appwidget provider from the given package. If the package contains more than
1341         * one appwidget provider, an arbitrary one is returned.
1342         */
1343        private ComponentName getProviderInPackage(String packageName) {
1344            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1345            List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1346            if (providers == null) return null;
1347            final int providerCount = providers.size();
1348            for (int i = 0; i < providerCount; i++) {
1349                ComponentName provider = providers.get(i).provider;
1350                if (provider != null && provider.getPackageName().equals(packageName)) {
1351                    return provider;
1352                }
1353            }
1354            return null;
1355        }
1356
1357        private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
1358            final ContentResolver resolver = mContext.getContentResolver();
1359            Cursor c = null;
1360            int count = 0;
1361            int curScreen = 0;
1362
1363            try {
1364                c = resolver.query(uri, null, null, null, "title ASC");
1365            } catch (Exception e) {
1366                // Ignore
1367            }
1368
1369            // We already have a favorites database in the old provider
1370            if (c != null) {
1371                try {
1372                    if (c.getCount() > 0) {
1373                        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1374                        final int intentIndex
1375                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1376                        final int titleIndex
1377                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1378                        final int iconTypeIndex
1379                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1380                        final int iconIndex
1381                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1382                        final int iconPackageIndex
1383                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1384                        final int iconResourceIndex
1385                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1386                        final int containerIndex
1387                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1388                        final int itemTypeIndex
1389                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1390                        final int screenIndex
1391                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1392                        final int cellXIndex
1393                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1394                        final int cellYIndex
1395                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1396                        final int uriIndex
1397                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1398                        final int displayModeIndex
1399                                = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
1400                        final int profileIndex
1401                                = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
1402
1403                        int i = 0;
1404                        int curX = 0;
1405                        int curY = 0;
1406
1407                        final LauncherAppState app = LauncherAppState.getInstance();
1408                        final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1409                        final int width = (int) grid.numColumns;
1410                        final int height = (int) grid.numRows;
1411                        final int hotseatWidth = (int) grid.numHotseatIcons;
1412
1413                        final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
1414
1415                        final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
1416                        final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
1417                        final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
1418
1419                        while (c.moveToNext()) {
1420                            final int itemType = c.getInt(itemTypeIndex);
1421                            if (itemType != Favorites.ITEM_TYPE_APPLICATION
1422                                    && itemType != Favorites.ITEM_TYPE_SHORTCUT
1423                                    && itemType != Favorites.ITEM_TYPE_FOLDER) {
1424                                continue;
1425                            }
1426
1427                            final int cellX = c.getInt(cellXIndex);
1428                            final int cellY = c.getInt(cellYIndex);
1429                            final int screen = c.getInt(screenIndex);
1430                            int container = c.getInt(containerIndex);
1431                            final String intentStr = c.getString(intentIndex);
1432
1433                            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
1434                            UserHandleCompat userHandle;
1435                            final long userSerialNumber;
1436                            if (profileIndex != -1 && !c.isNull(profileIndex)) {
1437                                userSerialNumber = c.getInt(profileIndex);
1438                                userHandle = userManager.getUserForSerialNumber(userSerialNumber);
1439                            } else {
1440                                // Default to the serial number of this user, for older
1441                                // shortcuts.
1442                                userHandle = UserHandleCompat.myUserHandle();
1443                                userSerialNumber = userManager.getSerialNumberForUser(userHandle);
1444                            }
1445                            Launcher.addDumpLog(TAG, "migrating \""
1446                                + c.getString(titleIndex) + "\" ("
1447                                + cellX + "," + cellY + "@"
1448                                + LauncherSettings.Favorites.containerToString(container)
1449                                + "/" + screen
1450                                + "): " + intentStr, true);
1451
1452                            if (itemType != Favorites.ITEM_TYPE_FOLDER) {
1453
1454                                final Intent intent;
1455                                final ComponentName cn;
1456                                try {
1457                                    intent = Intent.parseUri(intentStr, 0);
1458                                } catch (URISyntaxException e) {
1459                                    // bogus intent?
1460                                    Launcher.addDumpLog(TAG,
1461                                            "skipping invalid intent uri", true);
1462                                    continue;
1463                                }
1464
1465                                cn = intent.getComponent();
1466                                if (TextUtils.isEmpty(intentStr)) {
1467                                    // no intent? no icon
1468                                    Launcher.addDumpLog(TAG, "skipping empty intent", true);
1469                                    continue;
1470                                } else if (cn != null &&
1471                                        !LauncherModel.isValidPackageActivity(mContext, cn,
1472                                                userHandle)) {
1473                                    // component no longer exists.
1474                                    Launcher.addDumpLog(TAG, "skipping item whose component " +
1475                                            "no longer exists.", true);
1476                                    continue;
1477                                } else if (container ==
1478                                        LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1479                                    // Dedupe icons directly on the workspace
1480
1481                                    // Canonicalize
1482                                    // the Play Store sets the package parameter, but Launcher
1483                                    // does not, so we clear that out to keep them the same.
1484                                    // Also ignore intent flags for the purposes of deduping.
1485                                    intent.setPackage(null);
1486                                    int flags = intent.getFlags();
1487                                    intent.setFlags(0);
1488                                    final String key = intent.toUri(0);
1489                                    intent.setFlags(flags);
1490                                    if (seenIntents.contains(key)) {
1491                                        Launcher.addDumpLog(TAG, "skipping duplicate", true);
1492                                        continue;
1493                                    } else {
1494                                        seenIntents.add(key);
1495                                    }
1496                                }
1497                            }
1498
1499                            ContentValues values = new ContentValues(c.getColumnCount());
1500                            values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
1501                            values.put(LauncherSettings.Favorites.INTENT, intentStr);
1502                            values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
1503                            values.put(LauncherSettings.Favorites.ICON_TYPE,
1504                                    c.getInt(iconTypeIndex));
1505                            values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
1506                            values.put(LauncherSettings.Favorites.ICON_PACKAGE,
1507                                    c.getString(iconPackageIndex));
1508                            values.put(LauncherSettings.Favorites.ICON_RESOURCE,
1509                                    c.getString(iconResourceIndex));
1510                            values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
1511                            values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
1512                            values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
1513                            values.put(LauncherSettings.Favorites.DISPLAY_MODE,
1514                                    c.getInt(displayModeIndex));
1515                            values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
1516
1517                            if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1518                                hotseat.put(screen, values);
1519                            }
1520
1521                            if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1522                                // In a folder or in the hotseat, preserve position
1523                                values.put(LauncherSettings.Favorites.SCREEN, screen);
1524                                values.put(LauncherSettings.Favorites.CELLX, cellX);
1525                                values.put(LauncherSettings.Favorites.CELLY, cellY);
1526                            } else {
1527                                // For items contained directly on one of the workspace screen,
1528                                // we'll determine their location (screen, x, y) in a second pass.
1529                            }
1530
1531                            values.put(LauncherSettings.Favorites.CONTAINER, container);
1532
1533                            if (itemType != Favorites.ITEM_TYPE_FOLDER) {
1534                                shortcuts.add(values);
1535                            } else {
1536                                folders.add(values);
1537                            }
1538                        }
1539
1540                        // Now that we have all the hotseat icons, let's go through them left-right
1541                        // and assign valid locations for them in the new hotseat
1542                        final int N = hotseat.size();
1543                        for (int idx=0; idx<N; idx++) {
1544                            int hotseatX = hotseat.keyAt(idx);
1545                            ContentValues values = hotseat.valueAt(idx);
1546
1547                            if (hotseatX == grid.hotseatAllAppsRank) {
1548                                // let's drop this in the next available hole in the hotseat
1549                                while (++hotseatX < hotseatWidth) {
1550                                    if (hotseat.get(hotseatX) == null) {
1551                                        // found a spot! move it here
1552                                        values.put(LauncherSettings.Favorites.SCREEN,
1553                                                hotseatX);
1554                                        break;
1555                                    }
1556                                }
1557                            }
1558                            if (hotseatX >= hotseatWidth) {
1559                                // no room for you in the hotseat? it's off to the desktop with you
1560                                values.put(LauncherSettings.Favorites.CONTAINER,
1561                                           Favorites.CONTAINER_DESKTOP);
1562                            }
1563                        }
1564
1565                        final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
1566                        // Folders first
1567                        allItems.addAll(folders);
1568                        // Then shortcuts
1569                        allItems.addAll(shortcuts);
1570
1571                        // Layout all the folders
1572                        for (ContentValues values: allItems) {
1573                            if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
1574                                    LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1575                                // Hotseat items and folder items have already had their
1576                                // location information set. Nothing to be done here.
1577                                continue;
1578                            }
1579                            values.put(LauncherSettings.Favorites.SCREEN, curScreen);
1580                            values.put(LauncherSettings.Favorites.CELLX, curX);
1581                            values.put(LauncherSettings.Favorites.CELLY, curY);
1582                            curX = (curX + 1) % width;
1583                            if (curX == 0) {
1584                                curY = (curY + 1);
1585                            }
1586                            // Leave the last row of icons blank on every screen
1587                            if (curY == height - 1) {
1588                                curScreen = (int) generateNewScreenId();
1589                                curY = 0;
1590                            }
1591                        }
1592
1593                        if (allItems.size() > 0) {
1594                            db.beginTransaction();
1595                            try {
1596                                for (ContentValues row: allItems) {
1597                                    if (row == null) continue;
1598                                    if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
1599                                            < 0) {
1600                                        return;
1601                                    } else {
1602                                        count++;
1603                                    }
1604                                }
1605                                db.setTransactionSuccessful();
1606                            } finally {
1607                                db.endTransaction();
1608                            }
1609                        }
1610
1611                        db.beginTransaction();
1612                        try {
1613                            for (i=0; i<=curScreen; i++) {
1614                                final ContentValues values = new ContentValues();
1615                                values.put(LauncherSettings.WorkspaceScreens._ID, i);
1616                                values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1617                                if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
1618                                        < 0) {
1619                                    return;
1620                                }
1621                            }
1622                            db.setTransactionSuccessful();
1623                        } finally {
1624                            db.endTransaction();
1625                        }
1626                    }
1627                } finally {
1628                    c.close();
1629                }
1630            }
1631
1632            Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
1633                    + (curScreen+1) + " screens", true);
1634
1635            // ensure that new screens are created to hold these icons
1636            setFlagJustLoadedOldDb();
1637
1638            // Update max IDs; very important since we just grabbed IDs from another database
1639            mMaxItemId = initializeMaxItemId(db);
1640            mMaxScreenId = initializeMaxScreenId(db);
1641            if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
1642        }
1643    }
1644
1645    /**
1646     * Build a query string that will match any row where the column matches
1647     * anything in the values list.
1648     */
1649    private static String buildOrWhereString(String column, int[] values) {
1650        StringBuilder selectWhere = new StringBuilder();
1651        for (int i = values.length - 1; i >= 0; i--) {
1652            selectWhere.append(column).append("=").append(values[i]);
1653            if (i > 0) {
1654                selectWhere.append(" OR ");
1655            }
1656        }
1657        return selectWhere.toString();
1658    }
1659
1660    static class SqlArguments {
1661        public final String table;
1662        public final String where;
1663        public final String[] args;
1664
1665        SqlArguments(Uri url, String where, String[] args) {
1666            if (url.getPathSegments().size() == 1) {
1667                this.table = url.getPathSegments().get(0);
1668                this.where = where;
1669                this.args = args;
1670            } else if (url.getPathSegments().size() != 2) {
1671                throw new IllegalArgumentException("Invalid URI: " + url);
1672            } else if (!TextUtils.isEmpty(where)) {
1673                throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1674            } else {
1675                this.table = url.getPathSegments().get(0);
1676                this.where = "_id=" + ContentUris.parseId(url);
1677                this.args = null;
1678            }
1679        }
1680
1681        SqlArguments(Uri url) {
1682            if (url.getPathSegments().size() == 1) {
1683                table = url.getPathSegments().get(0);
1684                where = null;
1685                args = null;
1686            } else {
1687                throw new IllegalArgumentException("Invalid URI: " + url);
1688            }
1689        }
1690    }
1691}
1692