OSDN Git Service

resolved conflicts for merge of 13ef17a3 to mnc-dr-dev
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / LauncherProvider.java
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
17 package com.android.launcher3;
18
19 import android.annotation.TargetApi;
20 import android.appwidget.AppWidgetHost;
21 import android.appwidget.AppWidgetManager;
22 import android.content.ComponentName;
23 import android.content.ContentProvider;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentProviderResult;
26 import android.content.ContentResolver;
27 import android.content.ContentUris;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.OperationApplicationException;
32 import android.content.SharedPreferences;
33 import android.content.pm.PackageManager.NameNotFoundException;
34 import android.content.res.Resources;
35 import android.database.Cursor;
36 import android.database.SQLException;
37 import android.database.sqlite.SQLiteDatabase;
38 import android.database.sqlite.SQLiteOpenHelper;
39 import android.database.sqlite.SQLiteQueryBuilder;
40 import android.database.sqlite.SQLiteStatement;
41 import android.net.Uri;
42 import android.os.Binder;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.os.Process;
46 import android.os.StrictMode;
47 import android.os.UserManager;
48 import android.text.TextUtils;
49 import android.util.Log;
50 import android.util.SparseArray;
51
52 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
53 import com.android.launcher3.LauncherSettings.Favorites;
54 import com.android.launcher3.compat.UserHandleCompat;
55 import com.android.launcher3.compat.UserManagerCompat;
56 import com.android.launcher3.config.ProviderConfig;
57 import com.android.launcher3.util.ManagedProfileHeuristic;
58 import com.android.launcher3.util.Thunk;
59
60 import java.io.File;
61 import java.net.URISyntaxException;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.HashSet;
65 import java.util.List;
66
67 public class LauncherProvider extends ContentProvider {
68     private static final String TAG = "Launcher.LauncherProvider";
69     private static final boolean LOGD = false;
70
71     private static final int DATABASE_VERSION = 26;
72
73     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
74     static final String AUTHORITY = ProviderConfig.AUTHORITY;
75
76     static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
77     static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
78     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
79
80     private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
81
82     private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
83
84     @Thunk LauncherProviderChangeListener mListener;
85     @Thunk DatabaseHelper mOpenHelper;
86
87     @Override
88     public boolean onCreate() {
89         final Context context = getContext();
90         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
91         mOpenHelper = new DatabaseHelper(context);
92         StrictMode.setThreadPolicy(oldPolicy);
93         LauncherAppState.setLauncherProvider(this);
94         return true;
95     }
96
97     public boolean wasNewDbCreated() {
98         return mOpenHelper.wasNewDbCreated();
99     }
100
101     public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
102         mListener = listener;
103         mOpenHelper.mListener = mListener;
104     }
105
106     @Override
107     public String getType(Uri uri) {
108         SqlArguments args = new SqlArguments(uri, null, null);
109         if (TextUtils.isEmpty(args.where)) {
110             return "vnd.android.cursor.dir/" + args.table;
111         } else {
112             return "vnd.android.cursor.item/" + args.table;
113         }
114     }
115
116     @Override
117     public Cursor query(Uri uri, String[] projection, String selection,
118             String[] selectionArgs, String sortOrder) {
119
120         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
121         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
122         qb.setTables(args.table);
123
124         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
125         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
126         result.setNotificationUri(getContext().getContentResolver(), uri);
127
128         return result;
129     }
130
131     @Thunk static long dbInsertAndCheck(DatabaseHelper helper,
132             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
133         if (values == null) {
134             throw new RuntimeException("Error: attempting to insert null values");
135         }
136         if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
137             throw new RuntimeException("Error: attempting to add item without specifying an id");
138         }
139         helper.checkId(table, values);
140         return db.insert(table, nullColumnHack, values);
141     }
142
143     @Override
144     public Uri insert(Uri uri, ContentValues initialValues) {
145         SqlArguments args = new SqlArguments(uri);
146
147         // In very limited cases, we support system|signature permission apps to add to the db
148         String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
149         final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
150         if (isExternalAll) {
151             if (!mOpenHelper.initializeExternalAdd(initialValues)) {
152                 return null;
153             }
154         }
155
156         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
157         addModifiedTime(initialValues);
158         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
159         if (rowId < 0) return null;
160
161         uri = ContentUris.withAppendedId(uri, rowId);
162         notifyListeners();
163
164         if (isExternalAll) {
165             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
166             if (app != null) {
167                 app.reloadWorkspace();
168             }
169         }
170
171         return uri;
172     }
173
174
175     @Override
176     public int bulkInsert(Uri uri, ContentValues[] values) {
177         SqlArguments args = new SqlArguments(uri);
178
179         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
180         db.beginTransaction();
181         try {
182             int numValues = values.length;
183             for (int i = 0; i < numValues; i++) {
184                 addModifiedTime(values[i]);
185                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
186                     return 0;
187                 }
188             }
189             db.setTransactionSuccessful();
190         } finally {
191             db.endTransaction();
192         }
193
194         notifyListeners();
195         return values.length;
196     }
197
198     @Override
199     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
200             throws OperationApplicationException {
201         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
202         db.beginTransaction();
203         try {
204             ContentProviderResult[] result =  super.applyBatch(operations);
205             db.setTransactionSuccessful();
206             return result;
207         } finally {
208             db.endTransaction();
209         }
210     }
211
212     @Override
213     public int delete(Uri uri, String selection, String[] selectionArgs) {
214         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
215
216         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
217         int count = db.delete(args.table, args.where, args.args);
218         if (count > 0) notifyListeners();
219
220         return count;
221     }
222
223     @Override
224     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
225         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
226
227         addModifiedTime(values);
228         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
229         int count = db.update(args.table, values, args.where, args.args);
230         if (count > 0) notifyListeners();
231
232         return count;
233     }
234
235     @Override
236     public Bundle call(String method, String arg, Bundle extras) {
237         if (Binder.getCallingUid() != Process.myUid()) {
238             return null;
239         }
240
241         switch (method) {
242             case LauncherSettings.Settings.METHOD_GET_BOOLEAN: {
243                 Bundle result = new Bundle();
244                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
245                         getContext().getSharedPreferences(
246                                 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE)
247                                 .getBoolean(arg, extras.getBoolean(
248                                         LauncherSettings.Settings.EXTRA_DEFAULT_VALUE)));
249                 return result;
250             }
251             case LauncherSettings.Settings.METHOD_SET_BOOLEAN: {
252                 boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
253                 getContext().getSharedPreferences(
254                         LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE)
255                         .edit().putBoolean(arg, value).apply();
256                 if (mListener != null) {
257                     mListener.onSettingsChanged(arg, value);
258                 }
259                 Bundle result = new Bundle();
260                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, value);
261                 return result;
262             }
263         }
264         return null;
265     }
266
267     /**
268      * Deletes any empty folder from the DB.
269      * @return Ids of deleted folders.
270      */
271     public List<Long> deleteEmptyFolders() {
272         ArrayList<Long> folderIds = new ArrayList<Long>();
273         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
274         db.beginTransaction();
275         try {
276             // Select folders whose id do not match any container value.
277             String selection = LauncherSettings.Favorites.ITEM_TYPE + " = "
278                     + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND "
279                     + LauncherSettings.Favorites._ID +  " NOT IN (SELECT " +
280                             LauncherSettings.Favorites.CONTAINER + " FROM "
281                                 + TABLE_FAVORITES + ")";
282             Cursor c = db.query(TABLE_FAVORITES,
283                     new String[] {LauncherSettings.Favorites._ID},
284                     selection, null, null, null, null);
285             while (c.moveToNext()) {
286                 folderIds.add(c.getLong(0));
287             }
288             c.close();
289             if (folderIds.size() > 0) {
290                 db.delete(TABLE_FAVORITES, Utilities.createDbSelectionQuery(
291                         LauncherSettings.Favorites._ID, folderIds), null);
292             }
293             db.setTransactionSuccessful();
294         } catch (SQLException ex) {
295             Log.e(TAG, ex.getMessage(), ex);
296             folderIds.clear();
297         } finally {
298             db.endTransaction();
299         }
300         return folderIds;
301     }
302
303     private void notifyListeners() {
304         // always notify the backup agent
305         LauncherBackupAgentHelper.dataChanged(getContext());
306         if (mListener != null) {
307             mListener.onLauncherProviderChange();
308         }
309     }
310
311     @Thunk static void addModifiedTime(ContentValues values) {
312         values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
313     }
314
315     public long generateNewItemId() {
316         return mOpenHelper.generateNewItemId();
317     }
318
319     public void updateMaxItemId(long id) {
320         mOpenHelper.updateMaxItemId(id);
321     }
322
323     public long generateNewScreenId() {
324         return mOpenHelper.generateNewScreenId();
325     }
326
327     /**
328      * Clears all the data for a fresh start.
329      */
330     synchronized public void createEmptyDB() {
331         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
332     }
333
334     public void clearFlagEmptyDbCreated() {
335         String spKey = LauncherAppState.getSharedPreferencesKey();
336         getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE)
337             .edit()
338             .remove(EMPTY_DATABASE_CREATED)
339             .commit();
340     }
341
342     /**
343      * Loads the default workspace based on the following priority scheme:
344      *   1) From the app restrictions
345      *   2) From a package provided by play store
346      *   3) From a partner configuration APK, already in the system image
347      *   4) The default configuration for the particular device
348      */
349     synchronized public void loadDefaultFavoritesIfNecessary() {
350         String spKey = LauncherAppState.getSharedPreferencesKey();
351         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
352
353         if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
354             Log.d(TAG, "loading default workspace");
355
356             AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();
357             if (loader == null) {
358                 loader = AutoInstallsLayout.get(getContext(),
359                         mOpenHelper.mAppWidgetHost, mOpenHelper);
360             }
361             if (loader == null) {
362                 final Partner partner = Partner.get(getContext().getPackageManager());
363                 if (partner != null && partner.hasDefaultLayout()) {
364                     final Resources partnerRes = partner.getResources();
365                     int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
366                             "xml", partner.getPackageName());
367                     if (workspaceResId != 0) {
368                         loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
369                                 mOpenHelper, partnerRes, workspaceResId);
370                     }
371                 }
372             }
373
374             final boolean usingExternallyProvidedLayout = loader != null;
375             if (loader == null) {
376                 loader = getDefaultLayoutParser();
377             }
378
379             // There might be some partially restored DB items, due to buggy restore logic in
380             // previous versions of launcher.
381             createEmptyDB();
382             // Populate favorites table with initial favorites
383             if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
384                     && usingExternallyProvidedLayout) {
385                 // Unable to load external layout. Cleanup and load the internal layout.
386                 createEmptyDB();
387                 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
388                         getDefaultLayoutParser());
389             }
390             clearFlagEmptyDbCreated();
391         }
392     }
393
394     /**
395      * Creates workspace loader from an XML resource listed in the app restrictions.
396      *
397      * @return the loader if the restrictions are set and the resource exists; null otherwise.
398      */
399     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
400     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
401         // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
402         if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
403             return null;
404         }
405
406         Context ctx = getContext();
407         UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
408         Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
409         if (bundle == null) {
410             return null;
411         }
412
413         String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME);
414         if (packageName != null) {
415             try {
416                 Resources targetResources = ctx.getPackageManager()
417                         .getResourcesForApplication(packageName);
418                 return AutoInstallsLayout.get(ctx, packageName, targetResources,
419                         mOpenHelper.mAppWidgetHost, mOpenHelper);
420             } catch (NameNotFoundException e) {
421                 Log.e(TAG, "Target package for restricted profile not found", e);
422                 return null;
423             }
424         }
425         return null;
426     }
427
428     private DefaultLayoutParser getDefaultLayoutParser() {
429         int defaultLayout = LauncherAppState.getInstance()
430                 .getInvariantDeviceProfile().defaultLayoutId;
431         return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
432                 mOpenHelper, getContext().getResources(), defaultLayout);
433     }
434
435     public void migrateLauncher2Shortcuts() {
436         mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
437                 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
438     }
439
440     public void updateFolderItemsRank() {
441         mOpenHelper.updateFolderItemsRank(mOpenHelper.getWritableDatabase(), false);
442     }
443
444     public void convertShortcutsToLauncherActivities() {
445         mOpenHelper.convertShortcutsToLauncherActivities(mOpenHelper.getWritableDatabase());
446     }
447
448
449     public void deleteDatabase() {
450         // Are you sure? (y/n)
451         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
452         final File dbFile = new File(db.getPath());
453         mOpenHelper.close();
454         if (dbFile.exists()) {
455             SQLiteDatabase.deleteDatabase(dbFile);
456         }
457         mOpenHelper = new DatabaseHelper(getContext());
458         mOpenHelper.mListener = mListener;
459     }
460
461     private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
462         private final Context mContext;
463         @Thunk final AppWidgetHost mAppWidgetHost;
464         private long mMaxItemId = -1;
465         private long mMaxScreenId = -1;
466
467         private boolean mNewDbCreated = false;
468
469         @Thunk LauncherProviderChangeListener mListener;
470
471         DatabaseHelper(Context context) {
472             super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION);
473             mContext = context;
474             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
475
476             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
477             // the DB here
478             if (mMaxItemId == -1) {
479                 mMaxItemId = initializeMaxItemId(getWritableDatabase());
480             }
481             if (mMaxScreenId == -1) {
482                 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
483             }
484         }
485
486         public boolean wasNewDbCreated() {
487             return mNewDbCreated;
488         }
489
490         @Override
491         public void onCreate(SQLiteDatabase db) {
492             if (LOGD) Log.d(TAG, "creating new launcher database");
493
494             mMaxItemId = 1;
495             mMaxScreenId = 0;
496             mNewDbCreated = true;
497
498             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
499             long userSerialNumber = userManager.getSerialNumberForUser(
500                     UserHandleCompat.myUserHandle());
501
502             db.execSQL("CREATE TABLE favorites (" +
503                     "_id INTEGER PRIMARY KEY," +
504                     "title TEXT," +
505                     "intent TEXT," +
506                     "container INTEGER," +
507                     "screen INTEGER," +
508                     "cellX INTEGER," +
509                     "cellY INTEGER," +
510                     "spanX INTEGER," +
511                     "spanY INTEGER," +
512                     "itemType INTEGER," +
513                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
514                     "isShortcut INTEGER," +
515                     "iconType INTEGER," +
516                     "iconPackage TEXT," +
517                     "iconResource TEXT," +
518                     "icon BLOB," +
519                     "uri TEXT," +
520                     "displayMode INTEGER," +
521                     "appWidgetProvider TEXT," +
522                     "modified INTEGER NOT NULL DEFAULT 0," +
523                     "restored INTEGER NOT NULL DEFAULT 0," +
524                     "profileId INTEGER DEFAULT " + userSerialNumber + "," +
525                     "rank INTEGER NOT NULL DEFAULT 0," +
526                     "options INTEGER NOT NULL DEFAULT 0" +
527                     ");");
528             addWorkspacesTable(db);
529
530             // Database was just created, so wipe any previous widgets
531             if (mAppWidgetHost != null) {
532                 mAppWidgetHost.deleteHost();
533
534                 /**
535                  * Send notification that we've deleted the {@link AppWidgetHost},
536                  * probably as part of the initial database creation. The receiver may
537                  * want to re-call {@link AppWidgetHost#startListening()} to ensure
538                  * callbacks are correctly set.
539                  */
540                 new MainThreadExecutor().execute(new Runnable() {
541
542                     @Override
543                     public void run() {
544                         if (mListener != null) {
545                             mListener.onAppWidgetHostReset();
546                         }
547                     }
548                 });
549             }
550
551             // Fresh and clean launcher DB.
552             mMaxItemId = initializeMaxItemId(db);
553             setFlagEmptyDbCreated();
554
555             // When a new DB is created, remove all previously stored managed profile information.
556             ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(), mContext);
557         }
558
559         private void addWorkspacesTable(SQLiteDatabase db) {
560             db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
561                     LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," +
562                     LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
563                     LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
564                     ");");
565         }
566
567         private void removeOrphanedItems(SQLiteDatabase db) {
568             // Delete items directly on the workspace who's screen id doesn't exist
569             //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
570             //   AND container = -100"
571             String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
572                     " WHERE " +
573                     LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
574                     LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
575                     " AND " +
576                     LauncherSettings.Favorites.CONTAINER + " = " +
577                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
578             db.execSQL(removeOrphanedDesktopItems);
579
580             // Delete items contained in folders which no longer exist (after above statement)
581             //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
582             //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
583             String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
584                     " WHERE " +
585                     LauncherSettings.Favorites.CONTAINER + " <> " +
586                     LauncherSettings.Favorites.CONTAINER_DESKTOP +
587                     " AND "
588                     + LauncherSettings.Favorites.CONTAINER + " <> " +
589                     LauncherSettings.Favorites.CONTAINER_HOTSEAT +
590                     " AND "
591                     + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
592                     LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
593                     " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
594                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
595             db.execSQL(removeOrphanedFolderItems);
596         }
597
598         private void setFlagJustLoadedOldDb() {
599             String spKey = LauncherAppState.getSharedPreferencesKey();
600             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
601             sp.edit().putBoolean(EMPTY_DATABASE_CREATED, false).commit();
602         }
603
604         private void setFlagEmptyDbCreated() {
605             String spKey = LauncherAppState.getSharedPreferencesKey();
606             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
607             sp.edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
608         }
609
610         @Override
611         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
612             if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
613             switch (oldVersion) {
614                 // The version cannot be lower that 12, as Launcher3 never supported a lower
615                 // version of the DB.
616                 case 12: {
617                     // With the new shrink-wrapped and re-orderable workspaces, it makes sense
618                     // to persist workspace screens and their relative order.
619                     mMaxScreenId = 0;
620                     addWorkspacesTable(db);
621                 }
622                 case 13: {
623                     db.beginTransaction();
624                     try {
625                         // Insert new column for holding widget provider name
626                         db.execSQL("ALTER TABLE favorites " +
627                                 "ADD COLUMN appWidgetProvider TEXT;");
628                         db.setTransactionSuccessful();
629                     } catch (SQLException ex) {
630                         Log.e(TAG, ex.getMessage(), ex);
631                         // Old version remains, which means we wipe old data
632                         break;
633                     } finally {
634                         db.endTransaction();
635                     }
636                 }
637                 case 14: {
638                     db.beginTransaction();
639                     try {
640                         // Insert new column for holding update timestamp
641                         db.execSQL("ALTER TABLE favorites " +
642                                 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
643                         db.execSQL("ALTER TABLE workspaceScreens " +
644                                 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
645                         db.setTransactionSuccessful();
646                     } catch (SQLException ex) {
647                         Log.e(TAG, ex.getMessage(), ex);
648                         // Old version remains, which means we wipe old data
649                         break;
650                     } finally {
651                         db.endTransaction();
652                     }
653                 }
654                 case 15: {
655                     if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
656                         // Old version remains, which means we wipe old data
657                         break;
658                     }
659                 }
660                 case 16: {
661                     // We use the db version upgrade here to identify users who may not have seen
662                     // clings yet (because they weren't available), but for whom the clings are now
663                     // available (tablet users). Because one of the possible cling flows (migration)
664                     // is very destructive (wipes out workspaces), we want to prevent this from showing
665                     // until clear data. We do so by marking that the clings have been shown.
666                     LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
667                 }
668                 case 17: {
669                     // No-op
670                 }
671                 case 18: {
672                     // Due to a data loss bug, some users may have items associated with screen ids
673                     // which no longer exist. Since this can cause other problems, and since the user
674                     // will never see these items anyway, we use database upgrade as an opportunity to
675                     // clean things up.
676                     removeOrphanedItems(db);
677                 }
678                 case 19: {
679                     // Add userId column
680                     if (!addProfileColumn(db)) {
681                         // Old version remains, which means we wipe old data
682                         break;
683                     }
684                 }
685                 case 20:
686                     if (!updateFolderItemsRank(db, true)) {
687                         break;
688                     }
689                 case 21:
690                     // Recreate workspace table with screen id a primary key
691                     if (!recreateWorkspaceTable(db)) {
692                         break;
693                     }
694                 case 22: {
695                     if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
696                         // Old version remains, which means we wipe old data
697                         break;
698                     }
699                 }
700                 case 23:
701                     // No-op
702                 case 24:
703                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext);
704                 case 25:
705                     convertShortcutsToLauncherActivities(db);
706                 case 26: {
707                     // DB Upgraded successfully
708                     return;
709                 }
710             }
711
712             // DB was not upgraded
713             Log.w(TAG, "Destroying all old data.");
714             createEmptyDB(db);
715         }
716
717         @Override
718         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
719             // This shouldn't happen -- throw our hands up in the air and start over.
720             Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
721                     ". Wiping databse.");
722             createEmptyDB(db);
723         }
724
725         /**
726          * Clears all the data for a fresh start.
727          */
728         public void createEmptyDB(SQLiteDatabase db) {
729             db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
730             db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
731             onCreate(db);
732         }
733
734         /**
735          * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
736          * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
737          */
738         @Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
739             db.beginTransaction();
740             Cursor c = null;
741             SQLiteStatement updateStmt = null;
742
743             try {
744                 // Only consider the primary user as other users can't have a shortcut.
745                 long userSerial = UserManagerCompat.getInstance(mContext)
746                         .getSerialNumberForUser(UserHandleCompat.myUserHandle());
747                 c = db.query(TABLE_FAVORITES, new String[] {
748                         Favorites._ID,
749                         Favorites.INTENT,
750                     }, "itemType=" + Favorites.ITEM_TYPE_SHORTCUT + " AND profileId=" + userSerial,
751                     null, null, null, null);
752
753                 updateStmt = db.compileStatement("UPDATE favorites SET itemType="
754                         + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?");
755
756                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
757                 final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
758
759                 while (c.moveToNext()) {
760                     String intentDescription = c.getString(intentIndex);
761                     Intent intent;
762                     try {
763                         intent = Intent.parseUri(intentDescription, 0);
764                     } catch (URISyntaxException e) {
765                         Log.e(TAG, "Unable to parse intent", e);
766                         continue;
767                     }
768
769                     if (!Utilities.isLauncherAppTarget(intent)) {
770                         continue;
771                     }
772
773                     long id = c.getLong(idIndex);
774                     updateStmt.bindLong(1, id);
775                     updateStmt.executeUpdateDelete();
776                 }
777                 db.setTransactionSuccessful();
778             } catch (SQLException ex) {
779                 Log.w(TAG, "Error deduping shortcuts", ex);
780             } finally {
781                 db.endTransaction();
782                 if (c != null) {
783                     c.close();
784                 }
785                 if (updateStmt != null) {
786                     updateStmt.close();
787                 }
788             }
789         }
790
791         /**
792          * Recreates workspace table and migrates data to the new table.
793          */
794         public boolean recreateWorkspaceTable(SQLiteDatabase db) {
795             db.beginTransaction();
796             try {
797                 Cursor c = db.query(TABLE_WORKSPACE_SCREENS,
798                         new String[] {LauncherSettings.WorkspaceScreens._ID},
799                         null, null, null, null,
800                         LauncherSettings.WorkspaceScreens.SCREEN_RANK);
801                 ArrayList<Long> sortedIDs = new ArrayList<Long>();
802                 long maxId = 0;
803                 try {
804                     while (c.moveToNext()) {
805                         Long id = c.getLong(0);
806                         if (!sortedIDs.contains(id)) {
807                             sortedIDs.add(id);
808                             maxId = Math.max(maxId, id);
809                         }
810                     }
811                 } finally {
812                     c.close();
813                 }
814
815                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
816                 addWorkspacesTable(db);
817
818                 // Add all screen ids back
819                 int total = sortedIDs.size();
820                 for (int i = 0; i < total; i++) {
821                     ContentValues values = new ContentValues();
822                     values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i));
823                     values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
824                     addModifiedTime(values);
825                     db.insertOrThrow(TABLE_WORKSPACE_SCREENS, null, values);
826                 }
827                 db.setTransactionSuccessful();
828                 mMaxScreenId = maxId;
829             } catch (SQLException ex) {
830                 // Old version remains, which means we wipe old data
831                 Log.e(TAG, ex.getMessage(), ex);
832                 return false;
833             } finally {
834                 db.endTransaction();
835             }
836             return true;
837         }
838
839         @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
840             db.beginTransaction();
841             try {
842                 if (addRankColumn) {
843                     // Insert new column for holding rank
844                     db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
845                 }
846
847                 // Get a map for folder ID to folder width
848                 Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
849                         + " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
850                         + " GROUP BY container;",
851                         new String[] {Integer.toString(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)});
852
853                 while (c.moveToNext()) {
854                     db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
855                             + "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
856                             new Object[] {c.getLong(1) + 1, c.getLong(0)});
857                 }
858
859                 c.close();
860                 db.setTransactionSuccessful();
861             } catch (SQLException ex) {
862                 // Old version remains, which means we wipe old data
863                 Log.e(TAG, ex.getMessage(), ex);
864                 return false;
865             } finally {
866                 db.endTransaction();
867             }
868             return true;
869         }
870
871         private boolean addProfileColumn(SQLiteDatabase db) {
872             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
873             // Default to the serial number of this user, for older
874             // shortcuts.
875             long userSerialNumber = userManager.getSerialNumberForUser(
876                     UserHandleCompat.myUserHandle());
877             return addIntegerColumn(db, Favorites.PROFILE_ID, userSerialNumber);
878         }
879
880         private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
881             db.beginTransaction();
882             try {
883                 db.execSQL("ALTER TABLE favorites ADD COLUMN "
884                         + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
885                 db.setTransactionSuccessful();
886             } catch (SQLException ex) {
887                 Log.e(TAG, ex.getMessage(), ex);
888                 return false;
889             } finally {
890                 db.endTransaction();
891             }
892             return true;
893         }
894
895         // Generates a new ID to use for an object in your database. This method should be only
896         // called from the main UI thread. As an exception, we do call it when we call the
897         // constructor from the worker thread; however, this doesn't extend until after the
898         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
899         // after that point
900         @Override
901         public long generateNewItemId() {
902             if (mMaxItemId < 0) {
903                 throw new RuntimeException("Error: max item id was not initialized");
904             }
905             mMaxItemId += 1;
906             return mMaxItemId;
907         }
908
909         @Override
910         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
911             return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
912         }
913
914         public void updateMaxItemId(long id) {
915             mMaxItemId = id + 1;
916         }
917
918         public void checkId(String table, ContentValues values) {
919             long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
920             if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
921                 mMaxScreenId = Math.max(id, mMaxScreenId);
922             }  else {
923                 mMaxItemId = Math.max(id, mMaxItemId);
924             }
925         }
926
927         private long initializeMaxItemId(SQLiteDatabase db) {
928             return getMaxId(db, TABLE_FAVORITES);
929         }
930
931         // Generates a new ID to use for an workspace screen in your database. This method
932         // should be only called from the main UI thread. As an exception, we do call it when we
933         // call the constructor from the worker thread; however, this doesn't extend until after the
934         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
935         // after that point
936         public long generateNewScreenId() {
937             if (mMaxScreenId < 0) {
938                 throw new RuntimeException("Error: max screen id was not initialized");
939             }
940             mMaxScreenId += 1;
941             return mMaxScreenId;
942         }
943
944         private long initializeMaxScreenId(SQLiteDatabase db) {
945             return getMaxId(db, TABLE_WORKSPACE_SCREENS);
946         }
947
948         @Thunk boolean initializeExternalAdd(ContentValues values) {
949             // 1. Ensure that externally added items have a valid item id
950             long id = generateNewItemId();
951             values.put(LauncherSettings.Favorites._ID, id);
952
953             // 2. In the case of an app widget, and if no app widget id is specified, we
954             // attempt allocate and bind the widget.
955             Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
956             if (itemType != null &&
957                     itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
958                     !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
959
960                 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
961                 ComponentName cn = ComponentName.unflattenFromString(
962                         values.getAsString(Favorites.APPWIDGET_PROVIDER));
963
964                 if (cn != null) {
965                     try {
966                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
967                         values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
968                         if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
969                             return false;
970                         }
971                     } catch (RuntimeException e) {
972                         Log.e(TAG, "Failed to initialize external widget", e);
973                         return false;
974                     }
975                 } else {
976                     return false;
977                 }
978             }
979
980             // Add screen id if not present
981             long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
982             if (!addScreenIdIfNecessary(screenId)) {
983                 return false;
984             }
985             return true;
986         }
987
988         // Returns true of screen id exists, or if successfully added
989         private boolean addScreenIdIfNecessary(long screenId) {
990             if (!hasScreenId(screenId)) {
991                 int rank = getMaxScreenRank() + 1;
992
993                 ContentValues v = new ContentValues();
994                 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
995                 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
996                 if (dbInsertAndCheck(this, getWritableDatabase(),
997                         TABLE_WORKSPACE_SCREENS, null, v) < 0) {
998                     return false;
999                 }
1000             }
1001             return true;
1002         }
1003
1004         private boolean hasScreenId(long screenId) {
1005             SQLiteDatabase db = getWritableDatabase();
1006             Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1007                     + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1008             if (c != null) {
1009                 int count = c.getCount();
1010                 c.close();
1011                 return count > 0;
1012             } else {
1013                 return false;
1014             }
1015         }
1016
1017         private int getMaxScreenRank() {
1018             SQLiteDatabase db = getWritableDatabase();
1019             Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1020                     + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1021
1022             // get the result
1023             final int maxRankIndex = 0;
1024             int rank = -1;
1025             if (c != null && c.moveToNext()) {
1026                 rank = c.getInt(maxRankIndex);
1027             }
1028             if (c != null) {
1029                 c.close();
1030             }
1031
1032             return rank;
1033         }
1034
1035         @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
1036             ArrayList<Long> screenIds = new ArrayList<Long>();
1037             // TODO: Use multiple loaders with fall-back and transaction.
1038             int count = loader.loadLayout(db, screenIds);
1039
1040             // Add the screens specified by the items above
1041             Collections.sort(screenIds);
1042             int rank = 0;
1043             ContentValues values = new ContentValues();
1044             for (Long id : screenIds) {
1045                 values.clear();
1046                 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1047                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1048                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1049                     throw new RuntimeException("Failed initialize screen table"
1050                             + "from default layout");
1051                 }
1052                 rank++;
1053             }
1054
1055             // Ensure that the max ids are initialized
1056             mMaxItemId = initializeMaxItemId(db);
1057             mMaxScreenId = initializeMaxScreenId(db);
1058
1059             return count;
1060         }
1061
1062         @Thunk void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
1063             final ContentResolver resolver = mContext.getContentResolver();
1064             Cursor c = null;
1065             int count = 0;
1066             int curScreen = 0;
1067
1068             try {
1069                 c = resolver.query(uri, null, null, null, "title ASC");
1070             } catch (Exception e) {
1071                 // Ignore
1072             }
1073
1074             // We already have a favorites database in the old provider
1075             if (c != null) {
1076                 try {
1077                     if (c.getCount() > 0) {
1078                         final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1079                         final int intentIndex
1080                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1081                         final int titleIndex
1082                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1083                         final int iconTypeIndex
1084                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1085                         final int iconIndex
1086                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1087                         final int iconPackageIndex
1088                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1089                         final int iconResourceIndex
1090                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1091                         final int containerIndex
1092                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1093                         final int itemTypeIndex
1094                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1095                         final int screenIndex
1096                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1097                         final int cellXIndex
1098                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1099                         final int cellYIndex
1100                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1101                         final int uriIndex
1102                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1103                         final int displayModeIndex
1104                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
1105                         final int profileIndex
1106                                 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
1107
1108                         int i = 0;
1109                         int curX = 0;
1110                         int curY = 0;
1111
1112                         final LauncherAppState app = LauncherAppState.getInstance();
1113                         final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1114                         final int width = (int) profile.numColumns;
1115                         final int height = (int) profile.numRows;
1116                         final int hotseatWidth = (int) profile.numHotseatIcons;
1117
1118                         final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
1119
1120                         final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
1121                         final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
1122                         final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
1123
1124                         while (c.moveToNext()) {
1125                             final int itemType = c.getInt(itemTypeIndex);
1126                             if (itemType != Favorites.ITEM_TYPE_APPLICATION
1127                                     && itemType != Favorites.ITEM_TYPE_SHORTCUT
1128                                     && itemType != Favorites.ITEM_TYPE_FOLDER) {
1129                                 continue;
1130                             }
1131
1132                             final int cellX = c.getInt(cellXIndex);
1133                             final int cellY = c.getInt(cellYIndex);
1134                             final int screen = c.getInt(screenIndex);
1135                             int container = c.getInt(containerIndex);
1136                             final String intentStr = c.getString(intentIndex);
1137
1138                             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
1139                             UserHandleCompat userHandle;
1140                             final long userSerialNumber;
1141                             if (profileIndex != -1 && !c.isNull(profileIndex)) {
1142                                 userSerialNumber = c.getInt(profileIndex);
1143                                 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
1144                             } else {
1145                                 // Default to the serial number of this user, for older
1146                                 // shortcuts.
1147                                 userHandle = UserHandleCompat.myUserHandle();
1148                                 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
1149                             }
1150
1151                             if (userHandle == null) {
1152                                 Launcher.addDumpLog(TAG, "skipping deleted user", true);
1153                                 continue;
1154                             }
1155
1156                             Launcher.addDumpLog(TAG, "migrating \""
1157                                 + c.getString(titleIndex) + "\" ("
1158                                 + cellX + "," + cellY + "@"
1159                                 + LauncherSettings.Favorites.containerToString(container)
1160                                 + "/" + screen
1161                                 + "): " + intentStr, true);
1162
1163                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
1164
1165                                 final Intent intent;
1166                                 final ComponentName cn;
1167                                 try {
1168                                     intent = Intent.parseUri(intentStr, 0);
1169                                 } catch (URISyntaxException e) {
1170                                     // bogus intent?
1171                                     Launcher.addDumpLog(TAG,
1172                                             "skipping invalid intent uri", true);
1173                                     continue;
1174                                 }
1175
1176                                 cn = intent.getComponent();
1177                                 if (TextUtils.isEmpty(intentStr)) {
1178                                     // no intent? no icon
1179                                     Launcher.addDumpLog(TAG, "skipping empty intent", true);
1180                                     continue;
1181                                 } else if (cn != null &&
1182                                         !LauncherModel.isValidPackageActivity(mContext, cn,
1183                                                 userHandle)) {
1184                                     // component no longer exists.
1185                                     Launcher.addDumpLog(TAG, "skipping item whose component " +
1186                                             "no longer exists.", true);
1187                                     continue;
1188                                 } else if (container ==
1189                                         LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1190                                     // Dedupe icons directly on the workspace
1191
1192                                     // Canonicalize
1193                                     // the Play Store sets the package parameter, but Launcher
1194                                     // does not, so we clear that out to keep them the same.
1195                                     // Also ignore intent flags for the purposes of deduping.
1196                                     intent.setPackage(null);
1197                                     int flags = intent.getFlags();
1198                                     intent.setFlags(0);
1199                                     final String key = intent.toUri(0);
1200                                     intent.setFlags(flags);
1201                                     if (seenIntents.contains(key)) {
1202                                         Launcher.addDumpLog(TAG, "skipping duplicate", true);
1203                                         continue;
1204                                     } else {
1205                                         seenIntents.add(key);
1206                                     }
1207                                 }
1208                             }
1209
1210                             ContentValues values = new ContentValues(c.getColumnCount());
1211                             values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
1212                             values.put(LauncherSettings.Favorites.INTENT, intentStr);
1213                             values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
1214                             values.put(LauncherSettings.Favorites.ICON_TYPE,
1215                                     c.getInt(iconTypeIndex));
1216                             values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
1217                             values.put(LauncherSettings.Favorites.ICON_PACKAGE,
1218                                     c.getString(iconPackageIndex));
1219                             values.put(LauncherSettings.Favorites.ICON_RESOURCE,
1220                                     c.getString(iconResourceIndex));
1221                             values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
1222                             values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
1223                             values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
1224                             values.put(LauncherSettings.Favorites.DISPLAY_MODE,
1225                                     c.getInt(displayModeIndex));
1226                             values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
1227
1228                             if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1229                                 hotseat.put(screen, values);
1230                             }
1231
1232                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1233                                 // In a folder or in the hotseat, preserve position
1234                                 values.put(LauncherSettings.Favorites.SCREEN, screen);
1235                                 values.put(LauncherSettings.Favorites.CELLX, cellX);
1236                                 values.put(LauncherSettings.Favorites.CELLY, cellY);
1237                             } else {
1238                                 // For items contained directly on one of the workspace screen,
1239                                 // we'll determine their location (screen, x, y) in a second pass.
1240                             }
1241
1242                             values.put(LauncherSettings.Favorites.CONTAINER, container);
1243
1244                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
1245                                 shortcuts.add(values);
1246                             } else {
1247                                 folders.add(values);
1248                             }
1249                         }
1250
1251                         // Now that we have all the hotseat icons, let's go through them left-right
1252                         // and assign valid locations for them in the new hotseat
1253                         final int N = hotseat.size();
1254                         for (int idx=0; idx<N; idx++) {
1255                             int hotseatX = hotseat.keyAt(idx);
1256                             ContentValues values = hotseat.valueAt(idx);
1257
1258                             if (hotseatX == profile.hotseatAllAppsRank) {
1259                                 // let's drop this in the next available hole in the hotseat
1260                                 while (++hotseatX < hotseatWidth) {
1261                                     if (hotseat.get(hotseatX) == null) {
1262                                         // found a spot! move it here
1263                                         values.put(LauncherSettings.Favorites.SCREEN,
1264                                                 hotseatX);
1265                                         break;
1266                                     }
1267                                 }
1268                             }
1269                             if (hotseatX >= hotseatWidth) {
1270                                 // no room for you in the hotseat? it's off to the desktop with you
1271                                 values.put(LauncherSettings.Favorites.CONTAINER,
1272                                            Favorites.CONTAINER_DESKTOP);
1273                             }
1274                         }
1275
1276                         final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
1277                         // Folders first
1278                         allItems.addAll(folders);
1279                         // Then shortcuts
1280                         allItems.addAll(shortcuts);
1281
1282                         // Layout all the folders
1283                         for (ContentValues values: allItems) {
1284                             if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
1285                                     LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1286                                 // Hotseat items and folder items have already had their
1287                                 // location information set. Nothing to be done here.
1288                                 continue;
1289                             }
1290                             values.put(LauncherSettings.Favorites.SCREEN, curScreen);
1291                             values.put(LauncherSettings.Favorites.CELLX, curX);
1292                             values.put(LauncherSettings.Favorites.CELLY, curY);
1293                             curX = (curX + 1) % width;
1294                             if (curX == 0) {
1295                                 curY = (curY + 1);
1296                             }
1297                             // Leave the last row of icons blank on every screen
1298                             if (curY == height - 1) {
1299                                 curScreen = (int) generateNewScreenId();
1300                                 curY = 0;
1301                             }
1302                         }
1303
1304                         if (allItems.size() > 0) {
1305                             db.beginTransaction();
1306                             try {
1307                                 for (ContentValues row: allItems) {
1308                                     if (row == null) continue;
1309                                     if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
1310                                             < 0) {
1311                                         return;
1312                                     } else {
1313                                         count++;
1314                                     }
1315                                 }
1316                                 db.setTransactionSuccessful();
1317                             } finally {
1318                                 db.endTransaction();
1319                             }
1320                         }
1321
1322                         db.beginTransaction();
1323                         try {
1324                             for (i=0; i<=curScreen; i++) {
1325                                 final ContentValues values = new ContentValues();
1326                                 values.put(LauncherSettings.WorkspaceScreens._ID, i);
1327                                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1328                                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
1329                                         < 0) {
1330                                     return;
1331                                 }
1332                             }
1333                             db.setTransactionSuccessful();
1334                         } finally {
1335                             db.endTransaction();
1336                         }
1337
1338                         updateFolderItemsRank(db, false);
1339                     }
1340                 } finally {
1341                     c.close();
1342                 }
1343             }
1344
1345             Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
1346                     + (curScreen+1) + " screens", true);
1347
1348             // ensure that new screens are created to hold these icons
1349             setFlagJustLoadedOldDb();
1350
1351             // Update max IDs; very important since we just grabbed IDs from another database
1352             mMaxItemId = initializeMaxItemId(db);
1353             mMaxScreenId = initializeMaxScreenId(db);
1354             if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
1355         }
1356     }
1357
1358     /**
1359      * @return the max _id in the provided table.
1360      */
1361     @Thunk static long getMaxId(SQLiteDatabase db, String table) {
1362         Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
1363         // get the result
1364         long id = -1;
1365         if (c != null && c.moveToNext()) {
1366             id = c.getLong(0);
1367         }
1368         if (c != null) {
1369             c.close();
1370         }
1371
1372         if (id == -1) {
1373             throw new RuntimeException("Error: could not query max id in " + table);
1374         }
1375
1376         return id;
1377     }
1378
1379     static class SqlArguments {
1380         public final String table;
1381         public final String where;
1382         public final String[] args;
1383
1384         SqlArguments(Uri url, String where, String[] args) {
1385             if (url.getPathSegments().size() == 1) {
1386                 this.table = url.getPathSegments().get(0);
1387                 this.where = where;
1388                 this.args = args;
1389             } else if (url.getPathSegments().size() != 2) {
1390                 throw new IllegalArgumentException("Invalid URI: " + url);
1391             } else if (!TextUtils.isEmpty(where)) {
1392                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1393             } else {
1394                 this.table = url.getPathSegments().get(0);
1395                 this.where = "_id=" + ContentUris.parseId(url);
1396                 this.args = null;
1397             }
1398         }
1399
1400         SqlArguments(Uri url) {
1401             if (url.getPathSegments().size() == 1) {
1402                 table = url.getPathSegments().get(0);
1403                 where = null;
1404                 args = null;
1405             } else {
1406                 throw new IllegalArgumentException("Invalid URI: " + url);
1407             }
1408         }
1409     }
1410 }