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.app.SearchManager;
     20 import android.appwidget.AppWidgetHost;
     21 import android.appwidget.AppWidgetManager;
     22 import android.appwidget.AppWidgetProviderInfo;
     23 import android.content.ComponentName;
     24 import android.content.ContentProvider;
     25 import android.content.ContentProviderOperation;
     26 import android.content.ContentProviderResult;
     27 import android.content.ContentResolver;
     28 import android.content.ContentUris;
     29 import android.content.ContentValues;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.OperationApplicationException;
     33 import android.content.SharedPreferences;
     34 import android.content.pm.ActivityInfo;
     35 import android.content.pm.ApplicationInfo;
     36 import android.content.pm.PackageManager;
     37 import android.content.pm.ResolveInfo;
     38 import android.content.res.Resources;
     39 import android.content.res.XmlResourceParser;
     40 import android.database.Cursor;
     41 import android.database.SQLException;
     42 import android.database.sqlite.SQLiteDatabase;
     43 import android.database.sqlite.SQLiteOpenHelper;
     44 import android.database.sqlite.SQLiteQueryBuilder;
     45 import android.database.sqlite.SQLiteStatement;
     46 import android.graphics.Bitmap;
     47 import android.graphics.BitmapFactory;
     48 import android.net.Uri;
     49 import android.os.Bundle;
     50 import android.provider.Settings;
     51 import android.text.TextUtils;
     52 import android.util.Log;
     53 import android.util.SparseArray;
     54 
     55 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
     56 import com.android.launcher3.LauncherSettings.Favorites;
     57 import com.android.launcher3.compat.UserHandleCompat;
     58 import com.android.launcher3.compat.UserManagerCompat;
     59 import com.android.launcher3.config.ProviderConfig;
     60 
     61 import org.xmlpull.v1.XmlPullParser;
     62 import org.xmlpull.v1.XmlPullParserException;
     63 
     64 import java.io.File;
     65 import java.io.IOException;
     66 import java.net.URISyntaxException;
     67 import java.util.ArrayList;
     68 import java.util.Collections;
     69 import java.util.HashSet;
     70 import java.util.List;
     71 
     72 public class LauncherProvider extends ContentProvider {
     73     private static final String TAG = "Launcher.LauncherProvider";
     74     private static final boolean LOGD = false;
     75 
     76     private static final String DATABASE_NAME = "launcher.db";
     77 
     78     private static final int DATABASE_VERSION = 20;
     79 
     80     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     81     static final String AUTHORITY = ProviderConfig.AUTHORITY;
     82 
     83     // Should we attempt to load anything from the com.android.launcher2 provider?
     84     static final boolean IMPORT_LAUNCHER2_DATABASE = false;
     85 
     86     static final String TABLE_FAVORITES = "favorites";
     87     static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
     88     static final String PARAMETER_NOTIFY = "notify";
     89     static final String UPGRADED_FROM_OLD_DATABASE =
     90             "UPGRADED_FROM_OLD_DATABASE";
     91     static final String EMPTY_DATABASE_CREATED =
     92             "EMPTY_DATABASE_CREATED";
     93 
     94     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
     95             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
     96 
     97     private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
     98 
     99     private LauncherProviderChangeListener mListener;
    100 
    101     /**
    102      * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
    103      * {@link AppWidgetHost#deleteHost()} is called during database creation.
    104      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
    105      */
    106     static final Uri CONTENT_APPWIDGET_RESET_URI =
    107             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
    108 
    109     private DatabaseHelper mOpenHelper;
    110     private static boolean sJustLoadedFromOldDb;
    111 
    112     @Override
    113     public boolean onCreate() {
    114         final Context context = getContext();
    115         mOpenHelper = new DatabaseHelper(context);
    116         LauncherAppState.setLauncherProvider(this);
    117         return true;
    118     }
    119 
    120     public boolean wasNewDbCreated() {
    121         return mOpenHelper.wasNewDbCreated();
    122     }
    123 
    124     public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
    125         mListener = listener;
    126     }
    127 
    128     @Override
    129     public String getType(Uri uri) {
    130         SqlArguments args = new SqlArguments(uri, null, null);
    131         if (TextUtils.isEmpty(args.where)) {
    132             return "vnd.android.cursor.dir/" + args.table;
    133         } else {
    134             return "vnd.android.cursor.item/" + args.table;
    135         }
    136     }
    137 
    138     @Override
    139     public Cursor query(Uri uri, String[] projection, String selection,
    140             String[] selectionArgs, String sortOrder) {
    141 
    142         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    143         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    144         qb.setTables(args.table);
    145 
    146         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    147         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
    148         result.setNotificationUri(getContext().getContentResolver(), uri);
    149 
    150         return result;
    151     }
    152 
    153     private static long dbInsertAndCheck(DatabaseHelper helper,
    154             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
    155         if (values == null) {
    156             throw new RuntimeException("Error: attempting to insert null values");
    157         }
    158         if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
    159             throw new RuntimeException("Error: attempting to add item without specifying an id");
    160         }
    161         helper.checkId(table, values);
    162         return db.insert(table, nullColumnHack, values);
    163     }
    164 
    165     private static void deleteId(SQLiteDatabase db, long id) {
    166         Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
    167         SqlArguments args = new SqlArguments(uri, null, null);
    168         db.delete(args.table, args.where, args.args);
    169     }
    170 
    171     @Override
    172     public Uri insert(Uri uri, ContentValues initialValues) {
    173         SqlArguments args = new SqlArguments(uri);
    174 
    175         // In very limited cases, we support system|signature permission apps to add to the db
    176         String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
    177         if (externalAdd != null && "true".equals(externalAdd)) {
    178             if (!mOpenHelper.initializeExternalAdd(initialValues)) {
    179                 return null;
    180             }
    181         }
    182 
    183         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    184         addModifiedTime(initialValues);
    185         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
    186         if (rowId <= 0) return null;
    187 
    188         uri = ContentUris.withAppendedId(uri, rowId);
    189         sendNotify(uri);
    190 
    191         return uri;
    192     }
    193 
    194 
    195     @Override
    196     public int bulkInsert(Uri uri, ContentValues[] values) {
    197         SqlArguments args = new SqlArguments(uri);
    198 
    199         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    200         db.beginTransaction();
    201         try {
    202             int numValues = values.length;
    203             for (int i = 0; i < numValues; i++) {
    204                 addModifiedTime(values[i]);
    205                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
    206                     return 0;
    207                 }
    208             }
    209             db.setTransactionSuccessful();
    210         } finally {
    211             db.endTransaction();
    212         }
    213 
    214         sendNotify(uri);
    215         return values.length;
    216     }
    217 
    218     @Override
    219     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
    220             throws OperationApplicationException {
    221         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    222         db.beginTransaction();
    223         try {
    224             ContentProviderResult[] result =  super.applyBatch(operations);
    225             db.setTransactionSuccessful();
    226             return result;
    227         } finally {
    228             db.endTransaction();
    229         }
    230     }
    231 
    232     @Override
    233     public int delete(Uri uri, String selection, String[] selectionArgs) {
    234         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    235 
    236         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    237         int count = db.delete(args.table, args.where, args.args);
    238         if (count > 0) sendNotify(uri);
    239 
    240         return count;
    241     }
    242 
    243     @Override
    244     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    245         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    246 
    247         addModifiedTime(values);
    248         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    249         int count = db.update(args.table, values, args.where, args.args);
    250         if (count > 0) sendNotify(uri);
    251 
    252         return count;
    253     }
    254 
    255     private void sendNotify(Uri uri) {
    256         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
    257         if (notify == null || "true".equals(notify)) {
    258             getContext().getContentResolver().notifyChange(uri, null);
    259         }
    260 
    261         // always notify the backup agent
    262         LauncherBackupAgentHelper.dataChanged(getContext());
    263         if (mListener != null) {
    264             mListener.onLauncherProviderChange();
    265         }
    266     }
    267 
    268     private void addModifiedTime(ContentValues values) {
    269         values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
    270     }
    271 
    272     public long generateNewItemId() {
    273         return mOpenHelper.generateNewItemId();
    274     }
    275 
    276     public void updateMaxItemId(long id) {
    277         mOpenHelper.updateMaxItemId(id);
    278     }
    279 
    280     public long generateNewScreenId() {
    281         return mOpenHelper.generateNewScreenId();
    282     }
    283 
    284     // This is only required one time while loading the workspace during the
    285     // upgrade path, and should never be called from anywhere else.
    286     public void updateMaxScreenId(long maxScreenId) {
    287         mOpenHelper.updateMaxScreenId(maxScreenId);
    288     }
    289 
    290     /**
    291      * @param Should we load the old db for upgrade? first run only.
    292      */
    293     synchronized public boolean justLoadedOldDb() {
    294         String spKey = LauncherAppState.getSharedPreferencesKey();
    295         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
    296 
    297         boolean loadedOldDb = false || sJustLoadedFromOldDb;
    298 
    299         sJustLoadedFromOldDb = false;
    300         if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
    301 
    302             SharedPreferences.Editor editor = sp.edit();
    303             editor.remove(UPGRADED_FROM_OLD_DATABASE);
    304             editor.commit();
    305             loadedOldDb = true;
    306         }
    307         return loadedOldDb;
    308     }
    309 
    310     /**
    311      * Clears all the data for a fresh start.
    312      */
    313     synchronized public void createEmptyDB() {
    314         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
    315     }
    316 
    317     /**
    318      * Loads the default workspace based on the following priority scheme:
    319      *   1) From a package provided by play store
    320      *   2) From a partner configuration APK, already in the system image
    321      *   3) The default configuration for the particular device
    322      */
    323     synchronized public void loadDefaultFavoritesIfNecessary() {
    324         String spKey = LauncherAppState.getSharedPreferencesKey();
    325         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
    326 
    327         if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
    328             Log.d(TAG, "loading default workspace");
    329 
    330             WorkspaceLoader loader = AutoInstallsLayout.get(getContext(),
    331                     mOpenHelper.mAppWidgetHost, mOpenHelper);
    332 
    333             if (loader == null) {
    334                 final Partner partner = Partner.get(getContext().getPackageManager());
    335                 if (partner != null && partner.hasDefaultLayout()) {
    336                     final Resources partnerRes = partner.getResources();
    337                     int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
    338                             "xml", partner.getPackageName());
    339                     if (workspaceResId != 0) {
    340                         loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId);
    341                     }
    342                 }
    343             }
    344 
    345             if (loader == null) {
    346                 loader = new SimpleWorkspaceLoader(mOpenHelper, getContext().getResources(),
    347                         getDefaultWorkspaceResourceId());
    348             }
    349 
    350             // Populate favorites table with initial favorites
    351             SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED);
    352             mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader);
    353             editor.commit();
    354         }
    355     }
    356 
    357     public void migrateLauncher2Shortcuts() {
    358         mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
    359                 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
    360     }
    361 
    362     private static int getDefaultWorkspaceResourceId() {
    363         LauncherAppState app = LauncherAppState.getInstance();
    364         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
    365         if (LauncherAppState.isDisableAllApps()) {
    366             return grid.defaultNoAllAppsLayoutId;
    367         } else {
    368             return grid.defaultLayoutId;
    369         }
    370     }
    371 
    372     private static interface ContentValuesCallback {
    373         public void onRow(ContentValues values);
    374     }
    375 
    376     private static boolean shouldImportLauncher2Database(Context context) {
    377         boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
    378 
    379         // We don't import the old databse for tablets, as the grid size has changed.
    380         return !isTablet && IMPORT_LAUNCHER2_DATABASE;
    381     }
    382 
    383     public void deleteDatabase() {
    384         // Are you sure? (y/n)
    385         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    386         final File dbFile = new File(db.getPath());
    387         mOpenHelper.close();
    388         if (dbFile.exists()) {
    389             SQLiteDatabase.deleteDatabase(dbFile);
    390         }
    391         mOpenHelper = new DatabaseHelper(getContext());
    392     }
    393 
    394     private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
    395         private static final String TAG_RESOLVE = "resolve";
    396         private static final String TAG_FAVORITES = "favorites";
    397         private static final String TAG_FAVORITE = "favorite";
    398         private static final String TAG_APPWIDGET = "appwidget";
    399         private static final String TAG_SHORTCUT = "shortcut";
    400         private static final String TAG_FOLDER = "folder";
    401         private static final String TAG_PARTNER_FOLDER = "partner-folder";
    402         private static final String TAG_EXTRA = "extra";
    403         private static final String TAG_INCLUDE = "include";
    404 
    405         // Style attrs -- "Favorite"
    406         private static final String ATTR_CLASS_NAME = "className";
    407         private static final String ATTR_PACKAGE_NAME = "packageName";
    408         private static final String ATTR_CONTAINER = "container";
    409         private static final String ATTR_SCREEN = "screen";
    410         private static final String ATTR_X = "x";
    411         private static final String ATTR_Y = "y";
    412         private static final String ATTR_SPAN_X = "spanX";
    413         private static final String ATTR_SPAN_Y = "spanY";
    414         private static final String ATTR_ICON = "icon";
    415         private static final String ATTR_TITLE = "title";
    416         private static final String ATTR_URI = "uri";
    417 
    418         // Style attrs -- "Include"
    419         private static final String ATTR_WORKSPACE = "workspace";
    420 
    421         // Style attrs -- "Extra"
    422         private static final String ATTR_KEY = "key";
    423         private static final String ATTR_VALUE = "value";
    424 
    425         private final Context mContext;
    426         private final PackageManager mPackageManager;
    427         private final AppWidgetHost mAppWidgetHost;
    428         private long mMaxItemId = -1;
    429         private long mMaxScreenId = -1;
    430 
    431         private boolean mNewDbCreated = false;
    432 
    433         DatabaseHelper(Context context) {
    434             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    435             mContext = context;
    436             mPackageManager = context.getPackageManager();
    437             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
    438 
    439             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
    440             // the DB here
    441             if (mMaxItemId == -1) {
    442                 mMaxItemId = initializeMaxItemId(getWritableDatabase());
    443             }
    444             if (mMaxScreenId == -1) {
    445                 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
    446             }
    447         }
    448 
    449         public boolean wasNewDbCreated() {
    450             return mNewDbCreated;
    451         }
    452 
    453         /**
    454          * Send notification that we've deleted the {@link AppWidgetHost},
    455          * probably as part of the initial database creation. The receiver may
    456          * want to re-call {@link AppWidgetHost#startListening()} to ensure
    457          * callbacks are correctly set.
    458          */
    459         private void sendAppWidgetResetNotify() {
    460             final ContentResolver resolver = mContext.getContentResolver();
    461             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
    462         }
    463 
    464         @Override
    465         public void onCreate(SQLiteDatabase db) {
    466             if (LOGD) Log.d(TAG, "creating new launcher database");
    467 
    468             mMaxItemId = 1;
    469             mMaxScreenId = 0;
    470             mNewDbCreated = true;
    471 
    472             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
    473             long userSerialNumber = userManager.getSerialNumberForUser(
    474                     UserHandleCompat.myUserHandle());
    475 
    476             db.execSQL("CREATE TABLE favorites (" +
    477                     "_id INTEGER PRIMARY KEY," +
    478                     "title TEXT," +
    479                     "intent TEXT," +
    480                     "container INTEGER," +
    481                     "screen INTEGER," +
    482                     "cellX INTEGER," +
    483                     "cellY INTEGER," +
    484                     "spanX INTEGER," +
    485                     "spanY INTEGER," +
    486                     "itemType INTEGER," +
    487                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
    488                     "isShortcut INTEGER," +
    489                     "iconType INTEGER," +
    490                     "iconPackage TEXT," +
    491                     "iconResource TEXT," +
    492                     "icon BLOB," +
    493                     "uri TEXT," +
    494                     "displayMode INTEGER," +
    495                     "appWidgetProvider TEXT," +
    496                     "modified INTEGER NOT NULL DEFAULT 0," +
    497                     "restored INTEGER NOT NULL DEFAULT 0," +
    498                     "profileId INTEGER DEFAULT " + userSerialNumber +
    499                     ");");
    500             addWorkspacesTable(db);
    501 
    502             // Database was just created, so wipe any previous widgets
    503             if (mAppWidgetHost != null) {
    504                 mAppWidgetHost.deleteHost();
    505                 sendAppWidgetResetNotify();
    506             }
    507 
    508             if (shouldImportLauncher2Database(mContext)) {
    509                 // Try converting the old database
    510                 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
    511                     public void onRow(ContentValues values) {
    512                         int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
    513                         if (container == Favorites.CONTAINER_DESKTOP) {
    514                             int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
    515                             screen = (int) upgradeLauncherDb_permuteScreens(screen);
    516                             values.put(LauncherSettings.Favorites.SCREEN, screen);
    517                         }
    518                     }
    519                 };
    520                 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
    521                         "/old_favorites?notify=true");
    522                 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
    523                     // Try and upgrade from the Launcher2 db
    524                     uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri));
    525                     if (!convertDatabase(db, uri, permuteScreensCb, false)) {
    526                         // If we fail, then set a flag to load the default workspace
    527                         setFlagEmptyDbCreated();
    528                         return;
    529                     }
    530                 }
    531                 // Right now, in non-default workspace cases, we want to run the final
    532                 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
    533                 // set that flag too.
    534                 setFlagJustLoadedOldDb();
    535             } else {
    536                 // Fresh and clean launcher DB.
    537                 mMaxItemId = initializeMaxItemId(db);
    538                 setFlagEmptyDbCreated();
    539             }
    540         }
    541 
    542         private void addWorkspacesTable(SQLiteDatabase db) {
    543             db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
    544                     LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
    545                     LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
    546                     LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
    547                     ");");
    548         }
    549 
    550         private void removeOrphanedItems(SQLiteDatabase db) {
    551             // Delete items directly on the workspace who's screen id doesn't exist
    552             //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
    553             //   AND container = -100"
    554             String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
    555                     " WHERE " +
    556                     LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
    557                     LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
    558                     " AND " +
    559                     LauncherSettings.Favorites.CONTAINER + " = " +
    560                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
    561             db.execSQL(removeOrphanedDesktopItems);
    562 
    563             // Delete items contained in folders which no longer exist (after above statement)
    564             //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
    565             //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
    566             String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
    567                     " WHERE " +
    568                     LauncherSettings.Favorites.CONTAINER + " <> " +
    569                     LauncherSettings.Favorites.CONTAINER_DESKTOP +
    570                     " AND "
    571                     + LauncherSettings.Favorites.CONTAINER + " <> " +
    572                     LauncherSettings.Favorites.CONTAINER_HOTSEAT +
    573                     " AND "
    574                     + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
    575                     LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
    576                     " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
    577                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
    578             db.execSQL(removeOrphanedFolderItems);
    579         }
    580 
    581         private void setFlagJustLoadedOldDb() {
    582             String spKey = LauncherAppState.getSharedPreferencesKey();
    583             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    584             SharedPreferences.Editor editor = sp.edit();
    585             editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
    586             editor.putBoolean(EMPTY_DATABASE_CREATED, false);
    587             editor.commit();
    588         }
    589 
    590         private void setFlagEmptyDbCreated() {
    591             String spKey = LauncherAppState.getSharedPreferencesKey();
    592             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    593             SharedPreferences.Editor editor = sp.edit();
    594             editor.putBoolean(EMPTY_DATABASE_CREATED, true);
    595             editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
    596             editor.commit();
    597         }
    598 
    599         // We rearrange the screens from the old launcher
    600         // 12345 -> 34512
    601         private long upgradeLauncherDb_permuteScreens(long screen) {
    602             if (screen >= 2) {
    603                 return screen - 2;
    604             } else {
    605                 return screen + 3;
    606             }
    607         }
    608 
    609         private boolean convertDatabase(SQLiteDatabase db, Uri uri,
    610                                         ContentValuesCallback cb, boolean deleteRows) {
    611             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
    612             boolean converted = false;
    613 
    614             final ContentResolver resolver = mContext.getContentResolver();
    615             Cursor cursor = null;
    616 
    617             try {
    618                 cursor = resolver.query(uri, null, null, null, null);
    619             } catch (Exception e) {
    620                 // Ignore
    621             }
    622 
    623             // We already have a favorites database in the old provider
    624             if (cursor != null) {
    625                 try {
    626                      if (cursor.getCount() > 0) {
    627                         converted = copyFromCursor(db, cursor, cb) > 0;
    628                         if (converted && deleteRows) {
    629                             resolver.delete(uri, null, null);
    630                         }
    631                     }
    632                 } finally {
    633                     cursor.close();
    634                 }
    635             }
    636 
    637             if (converted) {
    638                 // Convert widgets from this import into widgets
    639                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
    640                 convertWidgets(db);
    641 
    642                 // Update max item id
    643                 mMaxItemId = initializeMaxItemId(db);
    644                 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
    645             }
    646 
    647             return converted;
    648         }
    649 
    650         private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
    651             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
    652             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
    653             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
    654             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
    655             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
    656             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
    657             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
    658             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
    659             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
    660             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
    661             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
    662             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
    663             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
    664             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
    665 
    666             ContentValues[] rows = new ContentValues[c.getCount()];
    667             int i = 0;
    668             while (c.moveToNext()) {
    669                 ContentValues values = new ContentValues(c.getColumnCount());
    670                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
    671                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
    672                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
    673                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
    674                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
    675                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
    676                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
    677                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
    678                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
    679                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
    680                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
    681                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
    682                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
    683                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
    684                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
    685                 if (cb != null) {
    686                     cb.onRow(values);
    687                 }
    688                 rows[i++] = values;
    689             }
    690 
    691             int total = 0;
    692             if (i > 0) {
    693                 db.beginTransaction();
    694                 try {
    695                     int numValues = rows.length;
    696                     for (i = 0; i < numValues; i++) {
    697                         if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
    698                             return 0;
    699                         } else {
    700                             total++;
    701                         }
    702                     }
    703                     db.setTransactionSuccessful();
    704                 } finally {
    705                     db.endTransaction();
    706                 }
    707             }
    708 
    709             return total;
    710         }
    711 
    712         @Override
    713         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    714             if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
    715 
    716             int version = oldVersion;
    717             if (version < 3) {
    718                 // upgrade 1,2 -> 3 added appWidgetId column
    719                 db.beginTransaction();
    720                 try {
    721                     // Insert new column for holding appWidgetIds
    722                     db.execSQL("ALTER TABLE favorites " +
    723                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
    724                     db.setTransactionSuccessful();
    725                     version = 3;
    726                 } catch (SQLException ex) {
    727                     // Old version remains, which means we wipe old data
    728                     Log.e(TAG, ex.getMessage(), ex);
    729                 } finally {
    730                     db.endTransaction();
    731                 }
    732 
    733                 // Convert existing widgets only if table upgrade was successful
    734                 if (version == 3) {
    735                     convertWidgets(db);
    736                 }
    737             }
    738 
    739             if (version < 4) {
    740                 version = 4;
    741             }
    742 
    743             // Where's version 5?
    744             // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
    745             // - Passion shipped on 2.1 with version 6 of launcher3
    746             // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
    747             //   but version 5 on there was the updateContactsShortcuts change
    748             //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
    749             // The updateContactsShortcuts change is idempotent, so running it twice
    750             // is okay so we'll do that when upgrading the devices that shipped with it.
    751             if (version < 6) {
    752                 // We went from 3 to 5 screens. Move everything 1 to the right
    753                 db.beginTransaction();
    754                 try {
    755                     db.execSQL("UPDATE favorites SET screen=(screen + 1);");
    756                     db.setTransactionSuccessful();
    757                 } catch (SQLException ex) {
    758                     // Old version remains, which means we wipe old data
    759                     Log.e(TAG, ex.getMessage(), ex);
    760                 } finally {
    761                     db.endTransaction();
    762                 }
    763 
    764                // We added the fast track.
    765                 if (updateContactsShortcuts(db)) {
    766                     version = 6;
    767                 }
    768             }
    769 
    770             if (version < 7) {
    771                 // Version 7 gets rid of the special search widget.
    772                 convertWidgets(db);
    773                 version = 7;
    774             }
    775 
    776             if (version < 8) {
    777                 // Version 8 (froyo) has the icons all normalized.  This should
    778                 // already be the case in practice, but we now rely on it and don't
    779                 // resample the images each time.
    780                 normalizeIcons(db);
    781                 version = 8;
    782             }
    783 
    784             if (version < 9) {
    785                 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
    786                 // before it gets a change to get set, so we need to read it here when we use it)
    787                 if (mMaxItemId == -1) {
    788                     mMaxItemId = initializeMaxItemId(db);
    789                 }
    790 
    791                 // Add default hotseat icons
    792                 loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(),
    793                         R.xml.update_workspace));
    794                 version = 9;
    795             }
    796 
    797             // We bumped the version three time during JB, once to update the launch flags, once to
    798             // update the override for the default launch animation and once to set the mimetype
    799             // to improve startup performance
    800             if (version < 12) {
    801                 // Contact shortcuts need a different set of flags to be launched now
    802                 // The updateContactsShortcuts change is idempotent, so we can keep using it like
    803                 // back in the Donut days
    804                 updateContactsShortcuts(db);
    805                 version = 12;
    806             }
    807 
    808             if (version < 13) {
    809                 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
    810                 // to persist workspace screens and their relative order.
    811                 mMaxScreenId = 0;
    812 
    813                 // This will never happen in the wild, but when we switch to using workspace
    814                 // screen ids, redo the import from old launcher.
    815                 sJustLoadedFromOldDb = true;
    816 
    817                 addWorkspacesTable(db);
    818                 version = 13;
    819             }
    820 
    821             if (version < 14) {
    822                 db.beginTransaction();
    823                 try {
    824                     // Insert new column for holding widget provider name
    825                     db.execSQL("ALTER TABLE favorites " +
    826                             "ADD COLUMN appWidgetProvider TEXT;");
    827                     db.setTransactionSuccessful();
    828                     version = 14;
    829                 } catch (SQLException ex) {
    830                     // Old version remains, which means we wipe old data
    831                     Log.e(TAG, ex.getMessage(), ex);
    832                 } finally {
    833                     db.endTransaction();
    834                 }
    835             }
    836 
    837             if (version < 15) {
    838                 db.beginTransaction();
    839                 try {
    840                     // Insert new column for holding update timestamp
    841                     db.execSQL("ALTER TABLE favorites " +
    842                             "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
    843                     db.execSQL("ALTER TABLE workspaceScreens " +
    844                             "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
    845                     db.setTransactionSuccessful();
    846                     version = 15;
    847                 } catch (SQLException ex) {
    848                     // Old version remains, which means we wipe old data
    849                     Log.e(TAG, ex.getMessage(), ex);
    850                 } finally {
    851                     db.endTransaction();
    852                 }
    853             }
    854 
    855 
    856             if (version < 16) {
    857                 db.beginTransaction();
    858                 try {
    859                     // Insert new column for holding restore status
    860                     db.execSQL("ALTER TABLE favorites " +
    861                             "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
    862                     db.setTransactionSuccessful();
    863                     version = 16;
    864                 } catch (SQLException ex) {
    865                     // Old version remains, which means we wipe old data
    866                     Log.e(TAG, ex.getMessage(), ex);
    867                 } finally {
    868                     db.endTransaction();
    869                 }
    870             }
    871 
    872             if (version < 17) {
    873                 // We use the db version upgrade here to identify users who may not have seen
    874                 // clings yet (because they weren't available), but for whom the clings are now
    875                 // available (tablet users). Because one of the possible cling flows (migration)
    876                 // is very destructive (wipes out workspaces), we want to prevent this from showing
    877                 // until clear data. We do so by marking that the clings have been shown.
    878                 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
    879                 version = 17;
    880             }
    881 
    882             if (version < 18) {
    883                 // No-op
    884                 version = 18;
    885             }
    886 
    887             if (version < 19) {
    888                 // Due to a data loss bug, some users may have items associated with screen ids
    889                 // which no longer exist. Since this can cause other problems, and since the user
    890                 // will never see these items anyway, we use database upgrade as an opportunity to
    891                 // clean things up.
    892                 removeOrphanedItems(db);
    893                 version = 19;
    894             }
    895 
    896             if (version < 20) {
    897                 // Add userId column
    898                 if (addProfileColumn(db)) {
    899                     version = 20;
    900                 }
    901                 // else old version remains, which means we wipe old data
    902             }
    903 
    904             if (version != DATABASE_VERSION) {
    905                 Log.w(TAG, "Destroying all old data.");
    906                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
    907                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
    908 
    909                 onCreate(db);
    910             }
    911         }
    912 
    913         @Override
    914         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    915             // This shouldn't happen -- throw our hands up in the air and start over.
    916             Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
    917                     ". Wiping databse.");
    918             createEmptyDB(db);
    919         }
    920 
    921 
    922         /**
    923          * Clears all the data for a fresh start.
    924          */
    925         public void createEmptyDB(SQLiteDatabase db) {
    926             db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
    927             db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
    928             onCreate(db);
    929         }
    930 
    931         private boolean addProfileColumn(SQLiteDatabase db) {
    932             db.beginTransaction();
    933             try {
    934                 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
    935                 // Default to the serial number of this user, for older
    936                 // shortcuts.
    937                 long userSerialNumber = userManager.getSerialNumberForUser(
    938                         UserHandleCompat.myUserHandle());
    939                 // Insert new column for holding user serial number
    940                 db.execSQL("ALTER TABLE favorites " +
    941                         "ADD COLUMN profileId INTEGER DEFAULT "
    942                                         + userSerialNumber + ";");
    943                 db.setTransactionSuccessful();
    944             } catch (SQLException ex) {
    945                 // Old version remains, which means we wipe old data
    946                 Log.e(TAG, ex.getMessage(), ex);
    947                 return false;
    948             } finally {
    949                 db.endTransaction();
    950             }
    951             return true;
    952         }
    953 
    954         private boolean updateContactsShortcuts(SQLiteDatabase db) {
    955             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
    956                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
    957 
    958             Cursor c = null;
    959             final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
    960             db.beginTransaction();
    961             try {
    962                 // Select and iterate through each matching widget
    963                 c = db.query(TABLE_FAVORITES,
    964                         new String[] { Favorites._ID, Favorites.INTENT },
    965                         selectWhere, null, null, null, null);
    966                 if (c == null) return false;
    967 
    968                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
    969 
    970                 final int idIndex = c.getColumnIndex(Favorites._ID);
    971                 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
    972 
    973                 while (c.moveToNext()) {
    974                     long favoriteId = c.getLong(idIndex);
    975                     final String intentUri = c.getString(intentIndex);
    976                     if (intentUri != null) {
    977                         try {
    978                             final Intent intent = Intent.parseUri(intentUri, 0);
    979                             android.util.Log.d("Home", intent.toString());
    980                             final Uri uri = intent.getData();
    981                             if (uri != null) {
    982                                 final String data = uri.toString();
    983                                 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
    984                                         actionQuickContact.equals(intent.getAction())) &&
    985                                         (data.startsWith("content://contacts/people/") ||
    986                                         data.startsWith("content://com.android.contacts/" +
    987                                                 "contacts/lookup/"))) {
    988 
    989                                     final Intent newIntent = new Intent(actionQuickContact);
    990                                     // When starting from the launcher, start in a new, cleared task
    991                                     // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
    992                                     // clear the whole thing preemptively here since
    993                                     // QuickContactActivity will finish itself when launching other
    994                                     // detail activities.
    995                                     newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    996                                             Intent.FLAG_ACTIVITY_CLEAR_TASK);
    997                                     newIntent.putExtra(
    998                                             Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
    999                                     newIntent.setData(uri);
   1000                                     // Determine the type and also put that in the shortcut
   1001                                     // (that can speed up launch a bit)
   1002                                     newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
   1003 
   1004                                     final ContentValues values = new ContentValues();
   1005                                     values.put(LauncherSettings.Favorites.INTENT,
   1006                                             newIntent.toUri(0));
   1007 
   1008                                     String updateWhere = Favorites._ID + "=" + favoriteId;
   1009                                     db.update(TABLE_FAVORITES, values, updateWhere, null);
   1010                                 }
   1011                             }
   1012                         } catch (RuntimeException ex) {
   1013                             Log.e(TAG, "Problem upgrading shortcut", ex);
   1014                         } catch (URISyntaxException e) {
   1015                             Log.e(TAG, "Problem upgrading shortcut", e);
   1016                         }
   1017                     }
   1018                 }
   1019 
   1020                 db.setTransactionSuccessful();
   1021             } catch (SQLException ex) {
   1022                 Log.w(TAG, "Problem while upgrading contacts", ex);
   1023                 return false;
   1024             } finally {
   1025                 db.endTransaction();
   1026                 if (c != null) {
   1027                     c.close();
   1028                 }
   1029             }
   1030 
   1031             return true;
   1032         }
   1033 
   1034         private void normalizeIcons(SQLiteDatabase db) {
   1035             Log.d(TAG, "normalizing icons");
   1036 
   1037             db.beginTransaction();
   1038             Cursor c = null;
   1039             SQLiteStatement update = null;
   1040             try {
   1041                 boolean logged = false;
   1042                 update = db.compileStatement("UPDATE favorites "
   1043                         + "SET icon=? WHERE _id=?");
   1044 
   1045                 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
   1046                         Favorites.ICON_TYPE_BITMAP, null);
   1047 
   1048                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
   1049                 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
   1050 
   1051                 while (c.moveToNext()) {
   1052                     long id = c.getLong(idIndex);
   1053                     byte[] data = c.getBlob(iconIndex);
   1054                     try {
   1055                         Bitmap bitmap = Utilities.resampleIconBitmap(
   1056                                 BitmapFactory.decodeByteArray(data, 0, data.length),
   1057                                 mContext);
   1058                         if (bitmap != null) {
   1059                             update.bindLong(1, id);
   1060                             data = ItemInfo.flattenBitmap(bitmap);
   1061                             if (data != null) {
   1062                                 update.bindBlob(2, data);
   1063                                 update.execute();
   1064                             }
   1065                             bitmap.recycle();
   1066                         }
   1067                     } catch (Exception e) {
   1068                         if (!logged) {
   1069                             Log.e(TAG, "Failed normalizing icon " + id, e);
   1070                         } else {
   1071                             Log.e(TAG, "Also failed normalizing icon " + id);
   1072                         }
   1073                         logged = true;
   1074                     }
   1075                 }
   1076                 db.setTransactionSuccessful();
   1077             } catch (SQLException ex) {
   1078                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
   1079             } finally {
   1080                 db.endTransaction();
   1081                 if (update != null) {
   1082                     update.close();
   1083                 }
   1084                 if (c != null) {
   1085                     c.close();
   1086                 }
   1087             }
   1088         }
   1089 
   1090         // Generates a new ID to use for an object in your database. This method should be only
   1091         // called from the main UI thread. As an exception, we do call it when we call the
   1092         // constructor from the worker thread; however, this doesn't extend until after the
   1093         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
   1094         // after that point
   1095         @Override
   1096         public long generateNewItemId() {
   1097             if (mMaxItemId < 0) {
   1098                 throw new RuntimeException("Error: max item id was not initialized");
   1099             }
   1100             mMaxItemId += 1;
   1101             return mMaxItemId;
   1102         }
   1103 
   1104         @Override
   1105         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
   1106             return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
   1107         }
   1108 
   1109         public void updateMaxItemId(long id) {
   1110             mMaxItemId = id + 1;
   1111         }
   1112 
   1113         public void checkId(String table, ContentValues values) {
   1114             long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
   1115             if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
   1116                 mMaxScreenId = Math.max(id, mMaxScreenId);
   1117             }  else {
   1118                 mMaxItemId = Math.max(id, mMaxItemId);
   1119             }
   1120         }
   1121 
   1122         private long initializeMaxItemId(SQLiteDatabase db) {
   1123             Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
   1124 
   1125             // get the result
   1126             final int maxIdIndex = 0;
   1127             long id = -1;
   1128             if (c != null && c.moveToNext()) {
   1129                 id = c.getLong(maxIdIndex);
   1130             }
   1131             if (c != null) {
   1132                 c.close();
   1133             }
   1134 
   1135             if (id == -1) {
   1136                 throw new RuntimeException("Error: could not query max item id");
   1137             }
   1138 
   1139             return id;
   1140         }
   1141 
   1142         // Generates a new ID to use for an workspace screen in your database. This method
   1143         // should be only called from the main UI thread. As an exception, we do call it when we
   1144         // call the constructor from the worker thread; however, this doesn't extend until after the
   1145         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
   1146         // after that point
   1147         public long generateNewScreenId() {
   1148             if (mMaxScreenId < 0) {
   1149                 throw new RuntimeException("Error: max screen id was not initialized");
   1150             }
   1151             mMaxScreenId += 1;
   1152             // Log to disk
   1153             Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
   1154             return mMaxScreenId;
   1155         }
   1156 
   1157         public void updateMaxScreenId(long maxScreenId) {
   1158             // Log to disk
   1159             Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
   1160             mMaxScreenId = maxScreenId;
   1161         }
   1162 
   1163         private long initializeMaxScreenId(SQLiteDatabase db) {
   1164             Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
   1165 
   1166             // get the result
   1167             final int maxIdIndex = 0;
   1168             long id = -1;
   1169             if (c != null && c.moveToNext()) {
   1170                 id = c.getLong(maxIdIndex);
   1171             }
   1172             if (c != null) {
   1173                 c.close();
   1174             }
   1175 
   1176             if (id == -1) {
   1177                 throw new RuntimeException("Error: could not query max screen id");
   1178             }
   1179 
   1180             // Log to disk
   1181             Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
   1182             return id;
   1183         }
   1184 
   1185         /**
   1186          * Upgrade existing clock and photo frame widgets into their new widget
   1187          * equivalents.
   1188          */
   1189         private void convertWidgets(SQLiteDatabase db) {
   1190             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
   1191             final int[] bindSources = new int[] {
   1192                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
   1193                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
   1194                     Favorites.ITEM_TYPE_WIDGET_SEARCH,
   1195             };
   1196 
   1197             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
   1198 
   1199             Cursor c = null;
   1200 
   1201             db.beginTransaction();
   1202             try {
   1203                 // Select and iterate through each matching widget
   1204                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
   1205                         selectWhere, null, null, null, null);
   1206 
   1207                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
   1208 
   1209                 final ContentValues values = new ContentValues();
   1210                 while (c != null && c.moveToNext()) {
   1211                     long favoriteId = c.getLong(0);
   1212                     int favoriteType = c.getInt(1);
   1213 
   1214                     // Allocate and update database with new appWidgetId
   1215                     try {
   1216                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
   1217 
   1218                         if (LOGD) {
   1219                             Log.d(TAG, "allocated appWidgetId=" + appWidgetId
   1220                                     + " for favoriteId=" + favoriteId);
   1221                         }
   1222                         values.clear();
   1223                         values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
   1224                         values.put(Favorites.APPWIDGET_ID, appWidgetId);
   1225 
   1226                         // Original widgets might not have valid spans when upgrading
   1227                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
   1228                             values.put(LauncherSettings.Favorites.SPANX, 4);
   1229                             values.put(LauncherSettings.Favorites.SPANY, 1);
   1230                         } else {
   1231                             values.put(LauncherSettings.Favorites.SPANX, 2);
   1232                             values.put(LauncherSettings.Favorites.SPANY, 2);
   1233                         }
   1234 
   1235                         String updateWhere = Favorites._ID + "=" + favoriteId;
   1236                         db.update(TABLE_FAVORITES, values, updateWhere, null);
   1237 
   1238                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
   1239                             // TODO: check return value
   1240                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
   1241                                     new ComponentName("com.android.alarmclock",
   1242                                     "com.android.alarmclock.AnalogAppWidgetProvider"));
   1243                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
   1244                             // TODO: check return value
   1245                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
   1246                                     new ComponentName("com.android.camera",
   1247                                     "com.android.camera.PhotoAppWidgetProvider"));
   1248                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
   1249                             // TODO: check return value
   1250                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
   1251                                     getSearchWidgetProvider());
   1252                         }
   1253                     } catch (RuntimeException ex) {
   1254                         Log.e(TAG, "Problem allocating appWidgetId", ex);
   1255                     }
   1256                 }
   1257 
   1258                 db.setTransactionSuccessful();
   1259             } catch (SQLException ex) {
   1260                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
   1261             } finally {
   1262                 db.endTransaction();
   1263                 if (c != null) {
   1264                     c.close();
   1265                 }
   1266             }
   1267 
   1268             // Update max item id
   1269             mMaxItemId = initializeMaxItemId(db);
   1270             if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
   1271         }
   1272 
   1273         private boolean initializeExternalAdd(ContentValues values) {
   1274             // 1. Ensure that externally added items have a valid item id
   1275             long id = generateNewItemId();
   1276             values.put(LauncherSettings.Favorites._ID, id);
   1277 
   1278             // 2. In the case of an app widget, and if no app widget id is specified, we
   1279             // attempt allocate and bind the widget.
   1280             Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
   1281             if (itemType != null &&
   1282                     itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
   1283                     !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
   1284 
   1285                 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
   1286                 ComponentName cn = ComponentName.unflattenFromString(
   1287                         values.getAsString(Favorites.APPWIDGET_PROVIDER));
   1288 
   1289                 if (cn != null) {
   1290                     try {
   1291                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
   1292                         values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
   1293                         if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
   1294                             return false;
   1295                         }
   1296                     } catch (RuntimeException e) {
   1297                         Log.e(TAG, "Failed to initialize external widget", e);
   1298                         return false;
   1299                     }
   1300                 } else {
   1301                     return false;
   1302                 }
   1303             }
   1304 
   1305             // Add screen id if not present
   1306             long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
   1307             if (!addScreenIdIfNecessary(screenId)) {
   1308                 return false;
   1309             }
   1310             return true;
   1311         }
   1312 
   1313         // Returns true of screen id exists, or if successfully added
   1314         private boolean addScreenIdIfNecessary(long screenId) {
   1315             if (!hasScreenId(screenId)) {
   1316                 int rank = getMaxScreenRank() + 1;
   1317 
   1318                 ContentValues v = new ContentValues();
   1319                 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
   1320                 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
   1321                 if (dbInsertAndCheck(this, getWritableDatabase(),
   1322                         TABLE_WORKSPACE_SCREENS, null, v) < 0) {
   1323                     return false;
   1324                 }
   1325             }
   1326             return true;
   1327         }
   1328 
   1329         private boolean hasScreenId(long screenId) {
   1330             SQLiteDatabase db = getWritableDatabase();
   1331             Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
   1332                     + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
   1333             if (c != null) {
   1334                 int count = c.getCount();
   1335                 c.close();
   1336                 return count > 0;
   1337             } else {
   1338                 return false;
   1339             }
   1340         }
   1341 
   1342         private int getMaxScreenRank() {
   1343             SQLiteDatabase db = getWritableDatabase();
   1344             Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
   1345                     + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
   1346 
   1347             // get the result
   1348             final int maxRankIndex = 0;
   1349             int rank = -1;
   1350             if (c != null && c.moveToNext()) {
   1351                 rank = c.getInt(maxRankIndex);
   1352             }
   1353             if (c != null) {
   1354                 c.close();
   1355             }
   1356 
   1357             return rank;
   1358         }
   1359 
   1360         private static final void beginDocument(XmlPullParser parser, String firstElementName)
   1361                 throws XmlPullParserException, IOException {
   1362             int type;
   1363             while ((type = parser.next()) != XmlPullParser.START_TAG
   1364                     && type != XmlPullParser.END_DOCUMENT) {
   1365                 ;
   1366             }
   1367 
   1368             if (type != XmlPullParser.START_TAG) {
   1369                 throw new XmlPullParserException("No start tag found");
   1370             }
   1371 
   1372             if (!parser.getName().equals(firstElementName)) {
   1373                 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
   1374                         ", expected " + firstElementName);
   1375             }
   1376         }
   1377 
   1378         private static Intent buildMainIntent() {
   1379             Intent intent = new Intent(Intent.ACTION_MAIN, null);
   1380             intent.addCategory(Intent.CATEGORY_LAUNCHER);
   1381             return intent;
   1382         }
   1383 
   1384         private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) {
   1385             ArrayList<Long> screenIds = new ArrayList<Long>();
   1386             // TODO: Use multiple loaders with fall-back and transaction.
   1387             int count = loader.loadLayout(db, screenIds);
   1388 
   1389             // Add the screens specified by the items above
   1390             Collections.sort(screenIds);
   1391             int rank = 0;
   1392             ContentValues values = new ContentValues();
   1393             for (Long id : screenIds) {
   1394                 values.clear();
   1395                 values.put(LauncherSettings.WorkspaceScreens._ID, id);
   1396                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
   1397                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
   1398                     throw new RuntimeException("Failed initialize screen table"
   1399                             + "from default layout");
   1400                 }
   1401                 rank++;
   1402             }
   1403 
   1404             // Ensure that the max ids are initialized
   1405             mMaxItemId = initializeMaxItemId(db);
   1406             mMaxScreenId = initializeMaxScreenId(db);
   1407 
   1408             return count;
   1409         }
   1410 
   1411         /**
   1412          * Loads the default set of favorite packages from an xml file.
   1413          *
   1414          * @param db The database to write the values into
   1415          * @param filterContainerId The specific container id of items to load
   1416          * @param the set of screenIds which are used by the favorites
   1417          */
   1418         private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId,
   1419                 ArrayList<Long> screenIds) {
   1420 
   1421             ContentValues values = new ContentValues();
   1422             if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
   1423 
   1424             int count = 0;
   1425             try {
   1426                 XmlResourceParser parser = res.getXml(workspaceResourceId);
   1427                 beginDocument(parser, TAG_FAVORITES);
   1428 
   1429                 final int depth = parser.getDepth();
   1430 
   1431                 int type;
   1432                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
   1433                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
   1434 
   1435                     if (type != XmlPullParser.START_TAG) {
   1436                         continue;
   1437                     }
   1438 
   1439                     boolean added = false;
   1440                     final String name = parser.getName();
   1441 
   1442                     if (TAG_INCLUDE.equals(name)) {
   1443 
   1444                         final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
   1445 
   1446                         if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
   1447                                 "", resId));
   1448 
   1449                         if (resId != 0 && resId != workspaceResourceId) {
   1450                             // recursively load some more favorites, why not?
   1451                             count += loadFavoritesRecursive(db, res, resId, screenIds);
   1452                             added = false;
   1453                         } else {
   1454                             Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
   1455                         }
   1456 
   1457                         if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
   1458                         continue;
   1459                     }
   1460 
   1461                     // Assuming it's a <favorite> at this point
   1462                     long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
   1463                     String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
   1464                     if (strContainer != null) {
   1465                         container = Long.valueOf(strContainer);
   1466                     }
   1467 
   1468                     String screen = getAttributeValue(parser, ATTR_SCREEN);
   1469                     String x = getAttributeValue(parser, ATTR_X);
   1470                     String y = getAttributeValue(parser, ATTR_Y);
   1471 
   1472                     values.clear();
   1473                     values.put(LauncherSettings.Favorites.CONTAINER, container);
   1474                     values.put(LauncherSettings.Favorites.SCREEN, screen);
   1475                     values.put(LauncherSettings.Favorites.CELLX, x);
   1476                     values.put(LauncherSettings.Favorites.CELLY, y);
   1477 
   1478                     if (LOGD) {
   1479                         final String title = getAttributeValue(parser, ATTR_TITLE);
   1480                         final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
   1481                         final String something = title != null ? title : pkg;
   1482                         Log.v(TAG, String.format(
   1483                                 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
   1484                                 "", name,
   1485                                 (something == null ? "" : (" \"" + something + "\"")),
   1486                                 container, screen, x, y));
   1487                     }
   1488 
   1489                     if (TAG_FAVORITE.equals(name)) {
   1490                         long id = addAppShortcut(db, values, parser);
   1491                         added = id >= 0;
   1492                     } else if (TAG_APPWIDGET.equals(name)) {
   1493                         added = addAppWidget(parser, type, db, values);
   1494                     } else if (TAG_SHORTCUT.equals(name)) {
   1495                         long id = addUriShortcut(db, values, res, parser);
   1496                         added = id >= 0;
   1497                     } else if (TAG_RESOLVE.equals(name)) {
   1498                         // This looks through the contained favorites (or meta-favorites) and
   1499                         // attempts to add them as shortcuts in the fallback group's location
   1500                         // until one is added successfully.
   1501                         added = false;
   1502                         final int groupDepth = parser.getDepth();
   1503                         while ((type = parser.next()) != XmlPullParser.END_TAG ||
   1504                                 parser.getDepth() > groupDepth) {
   1505                             if (type != XmlPullParser.START_TAG) {
   1506                                 continue;
   1507                             }
   1508                             final String fallback_item_name = parser.getName();
   1509                             if (!added) {
   1510                                 if (TAG_FAVORITE.equals(fallback_item_name)) {
   1511                                     final long id = addAppShortcut(db, values, parser);
   1512                                     added = id >= 0;
   1513                                 } else {
   1514                                     Log.e(TAG, "Fallback groups can contain only favorites, found "
   1515                                             + fallback_item_name);
   1516                                 }
   1517                             }
   1518                         }
   1519                     } else if (TAG_FOLDER.equals(name)) {
   1520                         // Folder contents are nested in this XML file
   1521                         added = loadFolder(db, values, res, parser);
   1522 
   1523                     } else if (TAG_PARTNER_FOLDER.equals(name)) {
   1524                         // Folder contents come from an external XML resource
   1525                         final Partner partner = Partner.get(mPackageManager);
   1526                         if (partner != null) {
   1527                             final Resources partnerRes = partner.getResources();
   1528                             final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
   1529                                     "xml", partner.getPackageName());
   1530                             if (resId != 0) {
   1531                                 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
   1532                                 beginDocument(partnerParser, TAG_FOLDER);
   1533                                 added = loadFolder(db, values, partnerRes, partnerParser);
   1534                             }
   1535                         }
   1536                     }
   1537                     if (added) {
   1538                         long screenId = Long.parseLong(screen);
   1539                         // Keep track of the set of screens which need to be added to the db.
   1540                         if (!screenIds.contains(screenId) &&
   1541                                 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   1542                             screenIds.add(screenId);
   1543                         }
   1544                         count++;
   1545                     }
   1546                 }
   1547             } catch (XmlPullParserException e) {
   1548                 Log.w(TAG, "Got exception parsing favorites.", e);
   1549             } catch (IOException e) {
   1550                 Log.w(TAG, "Got exception parsing favorites.", e);
   1551             } catch (RuntimeException e) {
   1552                 Log.w(TAG, "Got exception parsing favorites.", e);
   1553             }
   1554             return count;
   1555         }
   1556 
   1557         /**
   1558          * Parse folder items starting at {@link XmlPullParser} location. Allow recursive
   1559          * includes of items.
   1560          */
   1561         private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser,
   1562                 ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException {
   1563             int type;
   1564             int folderDepth = parser.getDepth();
   1565             while ((type = parser.next()) != XmlPullParser.END_TAG ||
   1566                     parser.getDepth() > folderDepth) {
   1567                 if (type != XmlPullParser.START_TAG) {
   1568                     continue;
   1569                 }
   1570                 final String tag = parser.getName();
   1571 
   1572                 final ContentValues childValues = new ContentValues();
   1573                 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
   1574 
   1575                 if (LOGD) {
   1576                     final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
   1577                     final String uri = getAttributeValue(parser, ATTR_URI);
   1578                     Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
   1579                             tag, uri != null ? uri : pkg));
   1580                 }
   1581 
   1582                 if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
   1583                     final long id = addAppShortcut(db, childValues, parser);
   1584                     if (id >= 0) {
   1585                         folderItems.add(id);
   1586                     }
   1587                 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
   1588                     final long id = addUriShortcut(db, childValues, res, parser);
   1589                     if (id >= 0) {
   1590                         folderItems.add(id);
   1591                     }
   1592                 } else if (TAG_INCLUDE.equals(tag) && folderId >= 0) {
   1593                     addToFolder(db, res, parser, folderItems, folderId);
   1594                 } else {
   1595                     throw new RuntimeException("Folders can contain only shortcuts");
   1596                 }
   1597             }
   1598         }
   1599 
   1600         /**
   1601          * Parse folder starting at current {@link XmlPullParser} location.
   1602          */
   1603         private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
   1604                 XmlResourceParser parser) throws IOException, XmlPullParserException {
   1605             final String title;
   1606             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
   1607             if (titleResId != 0) {
   1608                 title = res.getString(titleResId);
   1609             } else {
   1610                 title = mContext.getResources().getString(R.string.folder_name);
   1611             }
   1612 
   1613             values.put(LauncherSettings.Favorites.TITLE, title);
   1614             long folderId = addFolder(db, values);
   1615             boolean added = folderId >= 0;
   1616 
   1617             ArrayList<Long> folderItems = new ArrayList<Long>();
   1618             addToFolder(db, res, parser, folderItems, folderId);
   1619 
   1620             // We can only have folders with >= 2 items, so we need to remove the
   1621             // folder and clean up if less than 2 items were included, or some
   1622             // failed to add, and less than 2 were actually added
   1623             if (folderItems.size() < 2 && folderId >= 0) {
   1624                 // Delete the folder
   1625                 deleteId(db, folderId);
   1626 
   1627                 // If we have a single item, promote it to where the folder
   1628                 // would have been.
   1629                 if (folderItems.size() == 1) {
   1630                     final ContentValues childValues = new ContentValues();
   1631                     copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
   1632                     copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
   1633                     copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
   1634                     copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
   1635 
   1636                     final long id = folderItems.get(0);
   1637                     db.update(TABLE_FAVORITES, childValues,
   1638                             LauncherSettings.Favorites._ID + "=" + id, null);
   1639                 } else {
   1640                     added = false;
   1641                 }
   1642             }
   1643             return added;
   1644         }
   1645 
   1646         // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
   1647         // logical choice for what shortcut should be used for that intent exists, then it is
   1648         // added. Otherwise add nothing.
   1649         private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
   1650                 String intentUri) {
   1651             Intent metaIntent;
   1652             try {
   1653                 metaIntent = Intent.parseUri(intentUri, 0);
   1654             } catch (URISyntaxException e) {
   1655                 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e);
   1656                 return -1;
   1657             }
   1658 
   1659             ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
   1660                     PackageManager.MATCH_DEFAULT_ONLY);
   1661             final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
   1662                     metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
   1663 
   1664             // Verify that the result is an app and not just the resolver dialog asking which
   1665             // app to use.
   1666             if (wouldLaunchResolverActivity(resolved, appList)) {
   1667                 // If only one of the results is a system app then choose that as the default.
   1668                 final ResolveInfo systemApp = getSingleSystemActivity(appList);
   1669                 if (systemApp == null) {
   1670                     // There is no logical choice for this meta-favorite, so rather than making
   1671                     // a bad choice just add nothing.
   1672                     Log.w(TAG, "No preference or single system activity found for "
   1673                             + metaIntent.toString());
   1674                     return -1;
   1675                 }
   1676                 resolved = systemApp;
   1677             }
   1678             final ActivityInfo info = resolved.activityInfo;
   1679             final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
   1680             if (intent == null) {
   1681                 return -1;
   1682             }
   1683             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
   1684                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
   1685 
   1686             return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
   1687         }
   1688 
   1689         private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
   1690             ResolveInfo systemResolve = null;
   1691             final int N = appList.size();
   1692             for (int i = 0; i < N; ++i) {
   1693                 try {
   1694                     ApplicationInfo info = mPackageManager.getApplicationInfo(
   1695                             appList.get(i).activityInfo.packageName, 0);
   1696                     if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
   1697                         if (systemResolve != null) {
   1698                             return null;
   1699                         } else {
   1700                             systemResolve = appList.get(i);
   1701                         }
   1702                     }
   1703                 } catch (PackageManager.NameNotFoundException e) {
   1704                     Log.w(TAG, "Unable to get info about resolve results", e);
   1705                     return null;
   1706                 }
   1707             }
   1708             return systemResolve;
   1709         }
   1710 
   1711         private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
   1712                 List<ResolveInfo> appList) {
   1713             // If the list contains the above resolved activity, then it can't be
   1714             // ResolverActivity itself.
   1715             for (int i = 0; i < appList.size(); ++i) {
   1716                 ResolveInfo tmp = appList.get(i);
   1717                 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
   1718                         && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
   1719                     return false;
   1720                 }
   1721             }
   1722             return true;
   1723         }
   1724 
   1725         private long addAppShortcut(SQLiteDatabase db, ContentValues values,
   1726                 XmlResourceParser parser) {
   1727             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
   1728             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
   1729             final String uri = getAttributeValue(parser, ATTR_URI);
   1730 
   1731             if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
   1732                 ActivityInfo info;
   1733                 try {
   1734                     ComponentName cn;
   1735                     try {
   1736                         cn = new ComponentName(packageName, className);
   1737                         info = mPackageManager.getActivityInfo(cn, 0);
   1738                     } catch (PackageManager.NameNotFoundException nnfe) {
   1739                         String[] packages = mPackageManager.currentToCanonicalPackageNames(
   1740                                 new String[] { packageName });
   1741                         cn = new ComponentName(packages[0], className);
   1742                         info = mPackageManager.getActivityInfo(cn, 0);
   1743                     }
   1744                     final Intent intent = buildMainIntent();
   1745                     intent.setComponent(cn);
   1746                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
   1747                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
   1748 
   1749                     return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
   1750                             intent);
   1751                 } catch (PackageManager.NameNotFoundException e) {
   1752                     Log.w(TAG, "Unable to add favorite: " + packageName +
   1753                             "/" + className, e);
   1754                 }
   1755                 return -1;
   1756             } else if (!TextUtils.isEmpty(uri)) {
   1757                 // If no component specified try to find a shortcut to add from the URI.
   1758                 return addAppShortcutByUri(db, values, uri);
   1759             } else {
   1760                 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
   1761                 return -1;
   1762             }
   1763         }
   1764 
   1765         private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title,
   1766                 Intent intent) {
   1767             long id = generateNewItemId();
   1768             values.put(Favorites.INTENT, intent.toUri(0));
   1769             values.put(Favorites.TITLE, title);
   1770             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
   1771             values.put(Favorites.SPANX, 1);
   1772             values.put(Favorites.SPANY, 1);
   1773             values.put(Favorites._ID, id);
   1774             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
   1775                 return -1;
   1776             } else {
   1777                 return id;
   1778             }
   1779         }
   1780 
   1781         private long addFolder(SQLiteDatabase db, ContentValues values) {
   1782             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
   1783             values.put(Favorites.SPANX, 1);
   1784             values.put(Favorites.SPANY, 1);
   1785             long id = generateNewItemId();
   1786             values.put(Favorites._ID, id);
   1787             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
   1788                 return -1;
   1789             } else {
   1790                 return id;
   1791             }
   1792         }
   1793 
   1794         private ComponentName getSearchWidgetProvider() {
   1795             SearchManager searchManager =
   1796                     (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
   1797             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
   1798             if (searchComponent == null) return null;
   1799             return getProviderInPackage(searchComponent.getPackageName());
   1800         }
   1801 
   1802         /**
   1803          * Gets an appwidget provider from the given package. If the package contains more than
   1804          * one appwidget provider, an arbitrary one is returned.
   1805          */
   1806         private ComponentName getProviderInPackage(String packageName) {
   1807             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
   1808             List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
   1809             if (providers == null) return null;
   1810             final int providerCount = providers.size();
   1811             for (int i = 0; i < providerCount; i++) {
   1812                 ComponentName provider = providers.get(i).provider;
   1813                 if (provider != null && provider.getPackageName().equals(packageName)) {
   1814                     return provider;
   1815                 }
   1816             }
   1817             return null;
   1818         }
   1819 
   1820         private boolean addAppWidget(XmlResourceParser parser, int type,
   1821                 SQLiteDatabase db, ContentValues values)
   1822                 throws XmlPullParserException, IOException {
   1823 
   1824             String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
   1825             String className = getAttributeValue(parser, ATTR_CLASS_NAME);
   1826 
   1827             if (packageName == null || className == null) {
   1828                 return false;
   1829             }
   1830 
   1831             boolean hasPackage = true;
   1832             ComponentName cn = new ComponentName(packageName, className);
   1833             try {
   1834                 mPackageManager.getReceiverInfo(cn, 0);
   1835             } catch (Exception e) {
   1836                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
   1837                         new String[] { packageName });
   1838                 cn = new ComponentName(packages[0], className);
   1839                 try {
   1840                     mPackageManager.getReceiverInfo(cn, 0);
   1841                 } catch (Exception e1) {
   1842                     System.out.println("Can't find widget provider: " + className);
   1843                     hasPackage = false;
   1844                 }
   1845             }
   1846 
   1847             if (hasPackage) {
   1848                 String spanX = getAttributeValue(parser, ATTR_SPAN_X);
   1849                 String spanY = getAttributeValue(parser, ATTR_SPAN_Y);
   1850 
   1851                 values.put(Favorites.SPANX, spanX);
   1852                 values.put(Favorites.SPANY, spanY);
   1853 
   1854                 // Read the extras
   1855                 Bundle extras = new Bundle();
   1856                 int widgetDepth = parser.getDepth();
   1857                 while ((type = parser.next()) != XmlPullParser.END_TAG ||
   1858                         parser.getDepth() > widgetDepth) {
   1859                     if (type != XmlPullParser.START_TAG) {
   1860                         continue;
   1861                     }
   1862 
   1863                     if (TAG_EXTRA.equals(parser.getName())) {
   1864                         String key = getAttributeValue(parser, ATTR_KEY);
   1865                         String value = getAttributeValue(parser, ATTR_VALUE);
   1866                         if (key != null && value != null) {
   1867                             extras.putString(key, value);
   1868                         } else {
   1869                             throw new RuntimeException("Widget extras must have a key and value");
   1870                         }
   1871                     } else {
   1872                         throw new RuntimeException("Widgets can contain only extras");
   1873                     }
   1874                 }
   1875 
   1876                 return addAppWidget(db, values, cn, extras);
   1877             }
   1878 
   1879             return false;
   1880         }
   1881 
   1882         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
   1883                Bundle extras) {
   1884             boolean allocatedAppWidgets = false;
   1885             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
   1886 
   1887             try {
   1888                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
   1889 
   1890                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
   1891                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
   1892                 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
   1893                 values.put(Favorites._ID, generateNewItemId());
   1894                 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
   1895 
   1896                 allocatedAppWidgets = true;
   1897 
   1898                 // TODO: need to check return value
   1899                 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
   1900 
   1901                 // Send a broadcast to configure the widget
   1902                 if (extras != null && !extras.isEmpty()) {
   1903                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
   1904                     intent.setComponent(cn);
   1905                     intent.putExtras(extras);
   1906                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
   1907                     mContext.sendBroadcast(intent);
   1908                 }
   1909             } catch (RuntimeException ex) {
   1910                 Log.e(TAG, "Problem allocating appWidgetId", ex);
   1911             }
   1912 
   1913             return allocatedAppWidgets;
   1914         }
   1915 
   1916         private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
   1917                 XmlResourceParser parser) {
   1918             final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
   1919             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
   1920 
   1921             Intent intent;
   1922             String uri = null;
   1923             try {
   1924                 uri = getAttributeValue(parser, ATTR_URI);
   1925                 intent = Intent.parseUri(uri, 0);
   1926             } catch (URISyntaxException e) {
   1927                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
   1928                 return -1; // Oh well
   1929             }
   1930 
   1931             if (iconResId == 0 || titleResId == 0) {
   1932                 Log.w(TAG, "Shortcut is missing title or icon resource ID");
   1933                 return -1;
   1934             }
   1935 
   1936             long id = generateNewItemId();
   1937             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1938             values.put(Favorites.INTENT, intent.toUri(0));
   1939             values.put(Favorites.TITLE, res.getString(titleResId));
   1940             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
   1941             values.put(Favorites.SPANX, 1);
   1942             values.put(Favorites.SPANY, 1);
   1943             values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
   1944             values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
   1945             values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
   1946             values.put(Favorites._ID, id);
   1947 
   1948             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
   1949                 return -1;
   1950             }
   1951             return id;
   1952         }
   1953 
   1954         private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
   1955             final ContentResolver resolver = mContext.getContentResolver();
   1956             Cursor c = null;
   1957             int count = 0;
   1958             int curScreen = 0;
   1959 
   1960             try {
   1961                 c = resolver.query(uri, null, null, null, "title ASC");
   1962             } catch (Exception e) {
   1963                 // Ignore
   1964             }
   1965 
   1966             // We already have a favorites database in the old provider
   1967             if (c != null) {
   1968                 try {
   1969                     if (c.getCount() > 0) {
   1970                         final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
   1971                         final int intentIndex
   1972                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
   1973                         final int titleIndex
   1974                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
   1975                         final int iconTypeIndex
   1976                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
   1977                         final int iconIndex
   1978                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
   1979                         final int iconPackageIndex
   1980                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
   1981                         final int iconResourceIndex
   1982                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
   1983                         final int containerIndex
   1984                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
   1985                         final int itemTypeIndex
   1986                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
   1987                         final int screenIndex
   1988                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
   1989                         final int cellXIndex
   1990                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
   1991                         final int cellYIndex
   1992                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
   1993                         final int uriIndex
   1994                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
   1995                         final int displayModeIndex
   1996                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
   1997                         final int profileIndex
   1998                                 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
   1999 
   2000                         int i = 0;
   2001                         int curX = 0;
   2002                         int curY = 0;
   2003 
   2004                         final LauncherAppState app = LauncherAppState.getInstance();
   2005                         final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
   2006                         final int width = (int) grid.numColumns;
   2007                         final int height = (int) grid.numRows;
   2008                         final int hotseatWidth = (int) grid.numHotseatIcons;
   2009 
   2010                         final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
   2011 
   2012                         final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
   2013                         final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
   2014                         final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
   2015 
   2016                         while (c.moveToNext()) {
   2017                             final int itemType = c.getInt(itemTypeIndex);
   2018                             if (itemType != Favorites.ITEM_TYPE_APPLICATION
   2019                                     && itemType != Favorites.ITEM_TYPE_SHORTCUT
   2020                                     && itemType != Favorites.ITEM_TYPE_FOLDER) {
   2021                                 continue;
   2022                             }
   2023 
   2024                             final int cellX = c.getInt(cellXIndex);
   2025                             final int cellY = c.getInt(cellYIndex);
   2026                             final int screen = c.getInt(screenIndex);
   2027                             int container = c.getInt(containerIndex);
   2028                             final String intentStr = c.getString(intentIndex);
   2029 
   2030                             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
   2031                             UserHandleCompat userHandle;
   2032                             final long userSerialNumber;
   2033                             if (profileIndex != -1 && !c.isNull(profileIndex)) {
   2034                                 userSerialNumber = c.getInt(profileIndex);
   2035                                 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
   2036                             } else {
   2037                                 // Default to the serial number of this user, for older
   2038                                 // shortcuts.
   2039                                 userHandle = UserHandleCompat.myUserHandle();
   2040                                 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
   2041                             }
   2042                             Launcher.addDumpLog(TAG, "migrating \""
   2043                                 + c.getString(titleIndex) + "\" ("
   2044                                 + cellX + "," + cellY + "@"
   2045                                 + LauncherSettings.Favorites.containerToString(container)
   2046                                 + "/" + screen
   2047                                 + "): " + intentStr, true);
   2048 
   2049                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
   2050 
   2051                                 final Intent intent;
   2052                                 final ComponentName cn;
   2053                                 try {
   2054                                     intent = Intent.parseUri(intentStr, 0);
   2055                                 } catch (URISyntaxException e) {
   2056                                     // bogus intent?
   2057                                     Launcher.addDumpLog(TAG,
   2058                                             "skipping invalid intent uri", true);
   2059                                     continue;
   2060                                 }
   2061 
   2062                                 cn = intent.getComponent();
   2063                                 if (TextUtils.isEmpty(intentStr)) {
   2064                                     // no intent? no icon
   2065                                     Launcher.addDumpLog(TAG, "skipping empty intent", true);
   2066                                     continue;
   2067                                 } else if (cn != null &&
   2068                                         !LauncherModel.isValidPackageActivity(mContext, cn,
   2069                                                 userHandle)) {
   2070                                     // component no longer exists.
   2071                                     Launcher.addDumpLog(TAG, "skipping item whose component " +
   2072                                             "no longer exists.", true);
   2073                                     continue;
   2074                                 } else if (container ==
   2075                                         LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   2076                                     // Dedupe icons directly on the workspace
   2077 
   2078                                     // Canonicalize
   2079                                     // the Play Store sets the package parameter, but Launcher
   2080                                     // does not, so we clear that out to keep them the same
   2081                                     intent.setPackage(null);
   2082                                     final String key = intent.toUri(0);
   2083                                     if (seenIntents.contains(key)) {
   2084                                         Launcher.addDumpLog(TAG, "skipping duplicate", true);
   2085                                         continue;
   2086                                     } else {
   2087                                         seenIntents.add(key);
   2088                                     }
   2089                                 }
   2090                             }
   2091 
   2092                             ContentValues values = new ContentValues(c.getColumnCount());
   2093                             values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
   2094                             values.put(LauncherSettings.Favorites.INTENT, intentStr);
   2095                             values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
   2096                             values.put(LauncherSettings.Favorites.ICON_TYPE,
   2097                                     c.getInt(iconTypeIndex));
   2098                             values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
   2099                             values.put(LauncherSettings.Favorites.ICON_PACKAGE,
   2100                                     c.getString(iconPackageIndex));
   2101                             values.put(LauncherSettings.Favorites.ICON_RESOURCE,
   2102                                     c.getString(iconResourceIndex));
   2103                             values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
   2104                             values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
   2105                             values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
   2106                             values.put(LauncherSettings.Favorites.DISPLAY_MODE,
   2107                                     c.getInt(displayModeIndex));
   2108                             values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
   2109 
   2110                             if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
   2111                                 hotseat.put(screen, values);
   2112                             }
   2113 
   2114                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   2115                                 // In a folder or in the hotseat, preserve position
   2116                                 values.put(LauncherSettings.Favorites.SCREEN, screen);
   2117                                 values.put(LauncherSettings.Favorites.CELLX, cellX);
   2118                                 values.put(LauncherSettings.Favorites.CELLY, cellY);
   2119                             } else {
   2120                                 // For items contained directly on one of the workspace screen,
   2121                                 // we'll determine their location (screen, x, y) in a second pass.
   2122                             }
   2123 
   2124                             values.put(LauncherSettings.Favorites.CONTAINER, container);
   2125 
   2126                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
   2127                                 shortcuts.add(values);
   2128                             } else {
   2129                                 folders.add(values);
   2130                             }
   2131                         }
   2132 
   2133                         // Now that we have all the hotseat icons, let's go through them left-right
   2134                         // and assign valid locations for them in the new hotseat
   2135                         final int N = hotseat.size();
   2136                         for (int idx=0; idx<N; idx++) {
   2137                             int hotseatX = hotseat.keyAt(idx);
   2138                             ContentValues values = hotseat.valueAt(idx);
   2139 
   2140                             if (hotseatX == grid.hotseatAllAppsRank) {
   2141                                 // let's drop this in the next available hole in the hotseat
   2142                                 while (++hotseatX < hotseatWidth) {
   2143                                     if (hotseat.get(hotseatX) == null) {
   2144                                         // found a spot! move it here
   2145                                         values.put(LauncherSettings.Favorites.SCREEN,
   2146                                                 hotseatX);
   2147                                         break;
   2148                                     }
   2149                                 }
   2150                             }
   2151                             if (hotseatX >= hotseatWidth) {
   2152                                 // no room for you in the hotseat? it's off to the desktop with you
   2153                                 values.put(LauncherSettings.Favorites.CONTAINER,
   2154                                            Favorites.CONTAINER_DESKTOP);
   2155                             }
   2156                         }
   2157 
   2158                         final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
   2159                         // Folders first
   2160                         allItems.addAll(folders);
   2161                         // Then shortcuts
   2162                         allItems.addAll(shortcuts);
   2163 
   2164                         // Layout all the folders
   2165                         for (ContentValues values: allItems) {
   2166                             if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
   2167                                     LauncherSettings.Favorites.CONTAINER_DESKTOP) {
   2168                                 // Hotseat items and folder items have already had their
   2169                                 // location information set. Nothing to be done here.
   2170                                 continue;
   2171                             }
   2172                             values.put(LauncherSettings.Favorites.SCREEN, curScreen);
   2173                             values.put(LauncherSettings.Favorites.CELLX, curX);
   2174                             values.put(LauncherSettings.Favorites.CELLY, curY);
   2175                             curX = (curX + 1) % width;
   2176                             if (curX == 0) {
   2177                                 curY = (curY + 1);
   2178                             }
   2179                             // Leave the last row of icons blank on every screen
   2180                             if (curY == height - 1) {
   2181                                 curScreen = (int) generateNewScreenId();
   2182                                 curY = 0;
   2183                             }
   2184                         }
   2185 
   2186                         if (allItems.size() > 0) {
   2187                             db.beginTransaction();
   2188                             try {
   2189                                 for (ContentValues row: allItems) {
   2190                                     if (row == null) continue;
   2191                                     if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
   2192                                             < 0) {
   2193                                         return;
   2194                                     } else {
   2195                                         count++;
   2196                                     }
   2197                                 }
   2198                                 db.setTransactionSuccessful();
   2199                             } finally {
   2200                                 db.endTransaction();
   2201                             }
   2202                         }
   2203 
   2204                         db.beginTransaction();
   2205                         try {
   2206                             for (i=0; i<=curScreen; i++) {
   2207                                 final ContentValues values = new ContentValues();
   2208                                 values.put(LauncherSettings.WorkspaceScreens._ID, i);
   2209                                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
   2210                                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
   2211                                         < 0) {
   2212                                     return;
   2213                                 }
   2214                             }
   2215                             db.setTransactionSuccessful();
   2216                         } finally {
   2217                             db.endTransaction();
   2218                         }
   2219                     }
   2220                 } finally {
   2221                     c.close();
   2222                 }
   2223             }
   2224 
   2225             Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
   2226                     + (curScreen+1) + " screens", true);
   2227 
   2228             // ensure that new screens are created to hold these icons
   2229             setFlagJustLoadedOldDb();
   2230 
   2231             // Update max IDs; very important since we just grabbed IDs from another database
   2232             mMaxItemId = initializeMaxItemId(db);
   2233             mMaxScreenId = initializeMaxScreenId(db);
   2234             if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
   2235         }
   2236     }
   2237 
   2238     /**
   2239      * Build a query string that will match any row where the column matches
   2240      * anything in the values list.
   2241      */
   2242     private static String buildOrWhereString(String column, int[] values) {
   2243         StringBuilder selectWhere = new StringBuilder();
   2244         for (int i = values.length - 1; i >= 0; i--) {
   2245             selectWhere.append(column).append("=").append(values[i]);
   2246             if (i > 0) {
   2247                 selectWhere.append(" OR ");
   2248             }
   2249         }
   2250         return selectWhere.toString();
   2251     }
   2252 
   2253     /**
   2254      * Return attribute value, attempting launcher-specific namespace first
   2255      * before falling back to anonymous attribute.
   2256      */
   2257     private static String getAttributeValue(XmlResourceParser parser, String attribute) {
   2258         String value = parser.getAttributeValue(
   2259                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
   2260         if (value == null) {
   2261             value = parser.getAttributeValue(null, attribute);
   2262         }
   2263         return value;
   2264     }
   2265 
   2266     /**
   2267      * Return attribute resource value, attempting launcher-specific namespace
   2268      * first before falling back to anonymous attribute.
   2269      */
   2270     private static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
   2271             int defaultValue) {
   2272         int value = parser.getAttributeResourceValue(
   2273                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
   2274                 defaultValue);
   2275         if (value == defaultValue) {
   2276             value = parser.getAttributeResourceValue(null, attribute, defaultValue);
   2277         }
   2278         return value;
   2279     }
   2280 
   2281     private static void copyInteger(ContentValues from, ContentValues to, String key) {
   2282         to.put(key, from.getAsInteger(key));
   2283     }
   2284 
   2285     static class SqlArguments {
   2286         public final String table;
   2287         public final String where;
   2288         public final String[] args;
   2289 
   2290         SqlArguments(Uri url, String where, String[] args) {
   2291             if (url.getPathSegments().size() == 1) {
   2292                 this.table = url.getPathSegments().get(0);
   2293                 this.where = where;
   2294                 this.args = args;
   2295             } else if (url.getPathSegments().size() != 2) {
   2296                 throw new IllegalArgumentException("Invalid URI: " + url);
   2297             } else if (!TextUtils.isEmpty(where)) {
   2298                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
   2299             } else {
   2300                 this.table = url.getPathSegments().get(0);
   2301                 this.where = "_id=" + ContentUris.parseId(url);
   2302                 this.args = null;
   2303             }
   2304         }
   2305 
   2306         SqlArguments(Uri url) {
   2307             if (url.getPathSegments().size() == 1) {
   2308                 table = url.getPathSegments().get(0);
   2309                 where = null;
   2310                 args = null;
   2311             } else {
   2312                 throw new IllegalArgumentException("Invalid URI: " + url);
   2313             }
   2314         }
   2315     }
   2316 
   2317     static interface WorkspaceLoader {
   2318         /**
   2319          * @param screenIds A mutable list of screen its
   2320          * @return the number of workspace items added.
   2321          */
   2322         int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds);
   2323     }
   2324 
   2325     private static class SimpleWorkspaceLoader implements WorkspaceLoader {
   2326         private final Resources mRes;
   2327         private final int mWorkspaceId;
   2328         private final DatabaseHelper mHelper;
   2329 
   2330         SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) {
   2331             mHelper = helper;
   2332             mRes = res;
   2333             mWorkspaceId = workspaceId;
   2334         }
   2335 
   2336         @Override
   2337         public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
   2338             return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds);
   2339         }
   2340     }
   2341 }
   2342