Home | History | Annotate | Download | only in launcher3
      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 }
   1411