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