Home | History | Annotate | Download | only in launcher2
      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.launcher2;
     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.ContentResolver;
     26 import android.content.ContentUris;
     27 import android.content.ContentValues;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.SharedPreferences;
     31 import android.content.pm.ActivityInfo;
     32 import android.content.pm.PackageManager;
     33 import android.content.res.Resources;
     34 import android.content.res.TypedArray;
     35 import android.content.res.XmlResourceParser;
     36 import android.database.Cursor;
     37 import android.database.SQLException;
     38 import android.database.sqlite.SQLiteDatabase;
     39 import android.database.sqlite.SQLiteOpenHelper;
     40 import android.database.sqlite.SQLiteQueryBuilder;
     41 import android.database.sqlite.SQLiteStatement;
     42 import android.graphics.Bitmap;
     43 import android.graphics.BitmapFactory;
     44 import android.net.Uri;
     45 import android.os.Bundle;
     46 import android.provider.Settings;
     47 import android.text.TextUtils;
     48 import android.util.AttributeSet;
     49 import android.util.Log;
     50 import android.util.Xml;
     51 
     52 import com.android.launcher.R;
     53 import com.android.launcher2.LauncherSettings.Favorites;
     54 
     55 import org.xmlpull.v1.XmlPullParser;
     56 import org.xmlpull.v1.XmlPullParserException;
     57 
     58 import java.io.IOException;
     59 import java.net.URISyntaxException;
     60 import java.util.ArrayList;
     61 import java.util.List;
     62 
     63 public class LauncherProvider extends ContentProvider {
     64     private static final String TAG = "Launcher.LauncherProvider";
     65     private static final boolean LOGD = false;
     66 
     67     private static final String DATABASE_NAME = "launcher.db";
     68 
     69     private static final int DATABASE_VERSION = 12;
     70 
     71     static final String AUTHORITY = "com.android.launcher2.settings";
     72 
     73     static final String TABLE_FAVORITES = "favorites";
     74     static final String PARAMETER_NOTIFY = "notify";
     75     static final String DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED =
     76             "DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED";
     77 
     78     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
     79             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
     80 
     81     /**
     82      * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
     83      * {@link AppWidgetHost#deleteHost()} is called during database creation.
     84      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
     85      */
     86     static final Uri CONTENT_APPWIDGET_RESET_URI =
     87             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
     88 
     89     private DatabaseHelper mOpenHelper;
     90 
     91     @Override
     92     public boolean onCreate() {
     93         mOpenHelper = new DatabaseHelper(getContext());
     94         ((LauncherApplication) getContext()).setLauncherProvider(this);
     95         return true;
     96     }
     97 
     98     @Override
     99     public String getType(Uri uri) {
    100         SqlArguments args = new SqlArguments(uri, null, null);
    101         if (TextUtils.isEmpty(args.where)) {
    102             return "vnd.android.cursor.dir/" + args.table;
    103         } else {
    104             return "vnd.android.cursor.item/" + args.table;
    105         }
    106     }
    107 
    108     @Override
    109     public Cursor query(Uri uri, String[] projection, String selection,
    110             String[] selectionArgs, String sortOrder) {
    111 
    112         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    113         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    114         qb.setTables(args.table);
    115 
    116         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    117         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
    118         result.setNotificationUri(getContext().getContentResolver(), uri);
    119 
    120         return result;
    121     }
    122 
    123     private static long dbInsertAndCheck(DatabaseHelper helper,
    124             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
    125         if (!values.containsKey(LauncherSettings.Favorites._ID)) {
    126             throw new RuntimeException("Error: attempting to add item without specifying an id");
    127         }
    128         return db.insert(table, nullColumnHack, values);
    129     }
    130 
    131     private static void deleteId(SQLiteDatabase db, long id) {
    132         Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
    133         SqlArguments args = new SqlArguments(uri, null, null);
    134         db.delete(args.table, args.where, args.args);
    135     }
    136 
    137     @Override
    138     public Uri insert(Uri uri, ContentValues initialValues) {
    139         SqlArguments args = new SqlArguments(uri);
    140 
    141         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    142         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
    143         if (rowId <= 0) return null;
    144 
    145         uri = ContentUris.withAppendedId(uri, rowId);
    146         sendNotify(uri);
    147 
    148         return uri;
    149     }
    150 
    151     @Override
    152     public int bulkInsert(Uri uri, ContentValues[] values) {
    153         SqlArguments args = new SqlArguments(uri);
    154 
    155         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    156         db.beginTransaction();
    157         try {
    158             int numValues = values.length;
    159             for (int i = 0; i < numValues; i++) {
    160                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
    161                     return 0;
    162                 }
    163             }
    164             db.setTransactionSuccessful();
    165         } finally {
    166             db.endTransaction();
    167         }
    168 
    169         sendNotify(uri);
    170         return values.length;
    171     }
    172 
    173     @Override
    174     public int delete(Uri uri, String selection, String[] selectionArgs) {
    175         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    176 
    177         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    178         int count = db.delete(args.table, args.where, args.args);
    179         if (count > 0) sendNotify(uri);
    180 
    181         return count;
    182     }
    183 
    184     @Override
    185     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    186         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    187 
    188         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    189         int count = db.update(args.table, values, args.where, args.args);
    190         if (count > 0) sendNotify(uri);
    191 
    192         return count;
    193     }
    194 
    195     private void sendNotify(Uri uri) {
    196         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
    197         if (notify == null || "true".equals(notify)) {
    198             getContext().getContentResolver().notifyChange(uri, null);
    199         }
    200     }
    201 
    202     public long generateNewId() {
    203         return mOpenHelper.generateNewId();
    204     }
    205 
    206     synchronized public void loadDefaultFavoritesIfNecessary() {
    207         String spKey = LauncherApplication.getSharedPreferencesKey();
    208         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
    209         if (sp.getBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, false)) {
    210             // Populate favorites table with initial favorites
    211             SharedPreferences.Editor editor = sp.edit();
    212             editor.remove(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED);
    213             mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), R.xml.default_workspace);
    214             editor.commit();
    215         }
    216     }
    217 
    218     private static class DatabaseHelper extends SQLiteOpenHelper {
    219         private static final String TAG_FAVORITES = "favorites";
    220         private static final String TAG_FAVORITE = "favorite";
    221         private static final String TAG_CLOCK = "clock";
    222         private static final String TAG_SEARCH = "search";
    223         private static final String TAG_APPWIDGET = "appwidget";
    224         private static final String TAG_SHORTCUT = "shortcut";
    225         private static final String TAG_FOLDER = "folder";
    226         private static final String TAG_EXTRA = "extra";
    227 
    228         private final Context mContext;
    229         private final AppWidgetHost mAppWidgetHost;
    230         private long mMaxId = -1;
    231 
    232         DatabaseHelper(Context context) {
    233             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    234             mContext = context;
    235             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
    236 
    237             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
    238             // the DB here
    239             if (mMaxId == -1) {
    240                 mMaxId = initializeMaxId(getWritableDatabase());
    241             }
    242         }
    243 
    244         /**
    245          * Send notification that we've deleted the {@link AppWidgetHost},
    246          * probably as part of the initial database creation. The receiver may
    247          * want to re-call {@link AppWidgetHost#startListening()} to ensure
    248          * callbacks are correctly set.
    249          */
    250         private void sendAppWidgetResetNotify() {
    251             final ContentResolver resolver = mContext.getContentResolver();
    252             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
    253         }
    254 
    255         @Override
    256         public void onCreate(SQLiteDatabase db) {
    257             if (LOGD) Log.d(TAG, "creating new launcher database");
    258 
    259             mMaxId = 1;
    260 
    261             db.execSQL("CREATE TABLE favorites (" +
    262                     "_id INTEGER PRIMARY KEY," +
    263                     "title TEXT," +
    264                     "intent TEXT," +
    265                     "container INTEGER," +
    266                     "screen INTEGER," +
    267                     "cellX INTEGER," +
    268                     "cellY INTEGER," +
    269                     "spanX INTEGER," +
    270                     "spanY INTEGER," +
    271                     "itemType INTEGER," +
    272                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
    273                     "isShortcut INTEGER," +
    274                     "iconType INTEGER," +
    275                     "iconPackage TEXT," +
    276                     "iconResource TEXT," +
    277                     "icon BLOB," +
    278                     "uri TEXT," +
    279                     "displayMode INTEGER" +
    280                     ");");
    281 
    282             // Database was just created, so wipe any previous widgets
    283             if (mAppWidgetHost != null) {
    284                 mAppWidgetHost.deleteHost();
    285                 sendAppWidgetResetNotify();
    286             }
    287 
    288             if (!convertDatabase(db)) {
    289                 // Set a shared pref so that we know we need to load the default workspace later
    290                 setFlagToLoadDefaultWorkspaceLater();
    291             }
    292         }
    293 
    294         private void setFlagToLoadDefaultWorkspaceLater() {
    295             String spKey = LauncherApplication.getSharedPreferencesKey();
    296             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
    297             SharedPreferences.Editor editor = sp.edit();
    298             editor.putBoolean(DB_CREATED_BUT_DEFAULT_WORKSPACE_NOT_LOADED, true);
    299             editor.commit();
    300         }
    301 
    302         private boolean convertDatabase(SQLiteDatabase db) {
    303             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
    304             boolean converted = false;
    305 
    306             final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
    307                     "/old_favorites?notify=true");
    308             final ContentResolver resolver = mContext.getContentResolver();
    309             Cursor cursor = null;
    310 
    311             try {
    312                 cursor = resolver.query(uri, null, null, null, null);
    313             } catch (Exception e) {
    314                 // Ignore
    315             }
    316 
    317             // We already have a favorites database in the old provider
    318             if (cursor != null && cursor.getCount() > 0) {
    319                 try {
    320                     converted = copyFromCursor(db, cursor) > 0;
    321                 } finally {
    322                     cursor.close();
    323                 }
    324 
    325                 if (converted) {
    326                     resolver.delete(uri, null, null);
    327                 }
    328             }
    329 
    330             if (converted) {
    331                 // Convert widgets from this import into widgets
    332                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
    333                 convertWidgets(db);
    334             }
    335 
    336             return converted;
    337         }
    338 
    339         private int copyFromCursor(SQLiteDatabase db, Cursor c) {
    340             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
    341             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
    342             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
    343             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
    344             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
    345             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
    346             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
    347             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
    348             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
    349             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
    350             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
    351             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
    352             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
    353             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
    354 
    355             ContentValues[] rows = new ContentValues[c.getCount()];
    356             int i = 0;
    357             while (c.moveToNext()) {
    358                 ContentValues values = new ContentValues(c.getColumnCount());
    359                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
    360                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
    361                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
    362                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
    363                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
    364                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
    365                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
    366                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
    367                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
    368                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
    369                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
    370                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
    371                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
    372                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
    373                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
    374                 rows[i++] = values;
    375             }
    376 
    377             db.beginTransaction();
    378             int total = 0;
    379             try {
    380                 int numValues = rows.length;
    381                 for (i = 0; i < numValues; i++) {
    382                     if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
    383                         return 0;
    384                     } else {
    385                         total++;
    386                     }
    387                 }
    388                 db.setTransactionSuccessful();
    389             } finally {
    390                 db.endTransaction();
    391             }
    392 
    393             return total;
    394         }
    395 
    396         @Override
    397         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    398             if (LOGD) Log.d(TAG, "onUpgrade triggered");
    399 
    400             int version = oldVersion;
    401             if (version < 3) {
    402                 // upgrade 1,2 -> 3 added appWidgetId column
    403                 db.beginTransaction();
    404                 try {
    405                     // Insert new column for holding appWidgetIds
    406                     db.execSQL("ALTER TABLE favorites " +
    407                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
    408                     db.setTransactionSuccessful();
    409                     version = 3;
    410                 } catch (SQLException ex) {
    411                     // Old version remains, which means we wipe old data
    412                     Log.e(TAG, ex.getMessage(), ex);
    413                 } finally {
    414                     db.endTransaction();
    415                 }
    416 
    417                 // Convert existing widgets only if table upgrade was successful
    418                 if (version == 3) {
    419                     convertWidgets(db);
    420                 }
    421             }
    422 
    423             if (version < 4) {
    424                 version = 4;
    425             }
    426 
    427             // Where's version 5?
    428             // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
    429             // - Passion shipped on 2.1 with version 6 of launcher2
    430             // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
    431             //   but version 5 on there was the updateContactsShortcuts change
    432             //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
    433             // The updateContactsShortcuts change is idempotent, so running it twice
    434             // is okay so we'll do that when upgrading the devices that shipped with it.
    435             if (version < 6) {
    436                 // We went from 3 to 5 screens. Move everything 1 to the right
    437                 db.beginTransaction();
    438                 try {
    439                     db.execSQL("UPDATE favorites SET screen=(screen + 1);");
    440                     db.setTransactionSuccessful();
    441                 } catch (SQLException ex) {
    442                     // Old version remains, which means we wipe old data
    443                     Log.e(TAG, ex.getMessage(), ex);
    444                 } finally {
    445                     db.endTransaction();
    446                 }
    447 
    448                // We added the fast track.
    449                 if (updateContactsShortcuts(db)) {
    450                     version = 6;
    451                 }
    452             }
    453 
    454             if (version < 7) {
    455                 // Version 7 gets rid of the special search widget.
    456                 convertWidgets(db);
    457                 version = 7;
    458             }
    459 
    460             if (version < 8) {
    461                 // Version 8 (froyo) has the icons all normalized.  This should
    462                 // already be the case in practice, but we now rely on it and don't
    463                 // resample the images each time.
    464                 normalizeIcons(db);
    465                 version = 8;
    466             }
    467 
    468             if (version < 9) {
    469                 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
    470                 // before it gets a change to get set, so we need to read it here when we use it)
    471                 if (mMaxId == -1) {
    472                     mMaxId = initializeMaxId(db);
    473                 }
    474 
    475                 // Add default hotseat icons
    476                 loadFavorites(db, R.xml.update_workspace);
    477                 version = 9;
    478             }
    479 
    480             // We bumped the version three time during JB, once to update the launch flags, once to
    481             // update the override for the default launch animation and once to set the mimetype
    482             // to improve startup performance
    483             if (version < 12) {
    484                 // Contact shortcuts need a different set of flags to be launched now
    485                 // The updateContactsShortcuts change is idempotent, so we can keep using it like
    486                 // back in the Donut days
    487                 updateContactsShortcuts(db);
    488                 version = 12;
    489             }
    490 
    491             if (version != DATABASE_VERSION) {
    492                 Log.w(TAG, "Destroying all old data.");
    493                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
    494                 onCreate(db);
    495             }
    496         }
    497 
    498         private boolean updateContactsShortcuts(SQLiteDatabase db) {
    499             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
    500                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
    501 
    502             Cursor c = null;
    503             final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
    504             db.beginTransaction();
    505             try {
    506                 // Select and iterate through each matching widget
    507                 c = db.query(TABLE_FAVORITES,
    508                         new String[] { Favorites._ID, Favorites.INTENT },
    509                         selectWhere, null, null, null, null);
    510                 if (c == null) return false;
    511 
    512                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
    513 
    514                 final int idIndex = c.getColumnIndex(Favorites._ID);
    515                 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
    516 
    517                 while (c.moveToNext()) {
    518                     long favoriteId = c.getLong(idIndex);
    519                     final String intentUri = c.getString(intentIndex);
    520                     if (intentUri != null) {
    521                         try {
    522                             final Intent intent = Intent.parseUri(intentUri, 0);
    523                             android.util.Log.d("Home", intent.toString());
    524                             final Uri uri = intent.getData();
    525                             if (uri != null) {
    526                                 final String data = uri.toString();
    527                                 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
    528                                         actionQuickContact.equals(intent.getAction())) &&
    529                                         (data.startsWith("content://contacts/people/") ||
    530                                         data.startsWith("content://com.android.contacts/" +
    531                                                 "contacts/lookup/"))) {
    532 
    533                                     final Intent newIntent = new Intent(actionQuickContact);
    534                                     // When starting from the launcher, start in a new, cleared task
    535                                     // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
    536                                     // clear the whole thing preemptively here since
    537                                     // QuickContactActivity will finish itself when launching other
    538                                     // detail activities.
    539                                     newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    540                                             Intent.FLAG_ACTIVITY_CLEAR_TASK);
    541                                     newIntent.putExtra(
    542                                             Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
    543                                     newIntent.setData(uri);
    544                                     // Determine the type and also put that in the shortcut
    545                                     // (that can speed up launch a bit)
    546                                     newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
    547 
    548                                     final ContentValues values = new ContentValues();
    549                                     values.put(LauncherSettings.Favorites.INTENT,
    550                                             newIntent.toUri(0));
    551 
    552                                     String updateWhere = Favorites._ID + "=" + favoriteId;
    553                                     db.update(TABLE_FAVORITES, values, updateWhere, null);
    554                                 }
    555                             }
    556                         } catch (RuntimeException ex) {
    557                             Log.e(TAG, "Problem upgrading shortcut", ex);
    558                         } catch (URISyntaxException e) {
    559                             Log.e(TAG, "Problem upgrading shortcut", e);
    560                         }
    561                     }
    562                 }
    563 
    564                 db.setTransactionSuccessful();
    565             } catch (SQLException ex) {
    566                 Log.w(TAG, "Problem while upgrading contacts", ex);
    567                 return false;
    568             } finally {
    569                 db.endTransaction();
    570                 if (c != null) {
    571                     c.close();
    572                 }
    573             }
    574 
    575             return true;
    576         }
    577 
    578         private void normalizeIcons(SQLiteDatabase db) {
    579             Log.d(TAG, "normalizing icons");
    580 
    581             db.beginTransaction();
    582             Cursor c = null;
    583             SQLiteStatement update = null;
    584             try {
    585                 boolean logged = false;
    586                 update = db.compileStatement("UPDATE favorites "
    587                         + "SET icon=? WHERE _id=?");
    588 
    589                 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
    590                         Favorites.ICON_TYPE_BITMAP, null);
    591 
    592                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
    593                 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
    594 
    595                 while (c.moveToNext()) {
    596                     long id = c.getLong(idIndex);
    597                     byte[] data = c.getBlob(iconIndex);
    598                     try {
    599                         Bitmap bitmap = Utilities.resampleIconBitmap(
    600                                 BitmapFactory.decodeByteArray(data, 0, data.length),
    601                                 mContext);
    602                         if (bitmap != null) {
    603                             update.bindLong(1, id);
    604                             data = ItemInfo.flattenBitmap(bitmap);
    605                             if (data != null) {
    606                                 update.bindBlob(2, data);
    607                                 update.execute();
    608                             }
    609                             bitmap.recycle();
    610                         }
    611                     } catch (Exception e) {
    612                         if (!logged) {
    613                             Log.e(TAG, "Failed normalizing icon " + id, e);
    614                         } else {
    615                             Log.e(TAG, "Also failed normalizing icon " + id);
    616                         }
    617                         logged = true;
    618                     }
    619                 }
    620                 db.setTransactionSuccessful();
    621             } catch (SQLException ex) {
    622                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
    623             } finally {
    624                 db.endTransaction();
    625                 if (update != null) {
    626                     update.close();
    627                 }
    628                 if (c != null) {
    629                     c.close();
    630                 }
    631             }
    632         }
    633 
    634         // Generates a new ID to use for an object in your database. This method should be only
    635         // called from the main UI thread. As an exception, we do call it when we call the
    636         // constructor from the worker thread; however, this doesn't extend until after the
    637         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
    638         // after that point
    639         public long generateNewId() {
    640             if (mMaxId < 0) {
    641                 throw new RuntimeException("Error: max id was not initialized");
    642             }
    643             mMaxId += 1;
    644             return mMaxId;
    645         }
    646 
    647         private long initializeMaxId(SQLiteDatabase db) {
    648             Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
    649 
    650             // get the result
    651             final int maxIdIndex = 0;
    652             long id = -1;
    653             if (c != null && c.moveToNext()) {
    654                 id = c.getLong(maxIdIndex);
    655             }
    656             if (c != null) {
    657                 c.close();
    658             }
    659 
    660             if (id == -1) {
    661                 throw new RuntimeException("Error: could not query max id");
    662             }
    663 
    664             return id;
    665         }
    666 
    667         /**
    668          * Upgrade existing clock and photo frame widgets into their new widget
    669          * equivalents.
    670          */
    671         private void convertWidgets(SQLiteDatabase db) {
    672             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    673             final int[] bindSources = new int[] {
    674                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
    675                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
    676                     Favorites.ITEM_TYPE_WIDGET_SEARCH,
    677             };
    678 
    679             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
    680 
    681             Cursor c = null;
    682 
    683             db.beginTransaction();
    684             try {
    685                 // Select and iterate through each matching widget
    686                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
    687                         selectWhere, null, null, null, null);
    688 
    689                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
    690 
    691                 final ContentValues values = new ContentValues();
    692                 while (c != null && c.moveToNext()) {
    693                     long favoriteId = c.getLong(0);
    694                     int favoriteType = c.getInt(1);
    695 
    696                     // Allocate and update database with new appWidgetId
    697                     try {
    698                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
    699 
    700                         if (LOGD) {
    701                             Log.d(TAG, "allocated appWidgetId=" + appWidgetId
    702                                     + " for favoriteId=" + favoriteId);
    703                         }
    704                         values.clear();
    705                         values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
    706                         values.put(Favorites.APPWIDGET_ID, appWidgetId);
    707 
    708                         // Original widgets might not have valid spans when upgrading
    709                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
    710                             values.put(LauncherSettings.Favorites.SPANX, 4);
    711                             values.put(LauncherSettings.Favorites.SPANY, 1);
    712                         } else {
    713                             values.put(LauncherSettings.Favorites.SPANX, 2);
    714                             values.put(LauncherSettings.Favorites.SPANY, 2);
    715                         }
    716 
    717                         String updateWhere = Favorites._ID + "=" + favoriteId;
    718                         db.update(TABLE_FAVORITES, values, updateWhere, null);
    719 
    720                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
    721                             // TODO: check return value
    722                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
    723                                     new ComponentName("com.android.alarmclock",
    724                                     "com.android.alarmclock.AnalogAppWidgetProvider"));
    725                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
    726                             // TODO: check return value
    727                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
    728                                     new ComponentName("com.android.camera",
    729                                     "com.android.camera.PhotoAppWidgetProvider"));
    730                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
    731                             // TODO: check return value
    732                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
    733                                     getSearchWidgetProvider());
    734                         }
    735                     } catch (RuntimeException ex) {
    736                         Log.e(TAG, "Problem allocating appWidgetId", ex);
    737                     }
    738                 }
    739 
    740                 db.setTransactionSuccessful();
    741             } catch (SQLException ex) {
    742                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
    743             } finally {
    744                 db.endTransaction();
    745                 if (c != null) {
    746                     c.close();
    747                 }
    748             }
    749         }
    750 
    751         private static final void beginDocument(XmlPullParser parser, String firstElementName)
    752                 throws XmlPullParserException, IOException {
    753             int type;
    754             while ((type = parser.next()) != parser.START_TAG
    755                     && type != parser.END_DOCUMENT) {
    756                 ;
    757             }
    758 
    759             if (type != parser.START_TAG) {
    760                 throw new XmlPullParserException("No start tag found");
    761             }
    762 
    763             if (!parser.getName().equals(firstElementName)) {
    764                 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
    765                         ", expected " + firstElementName);
    766             }
    767         }
    768 
    769         /**
    770          * Loads the default set of favorite packages from an xml file.
    771          *
    772          * @param db The database to write the values into
    773          * @param filterContainerId The specific container id of items to load
    774          */
    775         private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
    776             Intent intent = new Intent(Intent.ACTION_MAIN, null);
    777             intent.addCategory(Intent.CATEGORY_LAUNCHER);
    778             ContentValues values = new ContentValues();
    779 
    780             PackageManager packageManager = mContext.getPackageManager();
    781             int allAppsButtonRank =
    782                     mContext.getResources().getInteger(R.integer.hotseat_all_apps_index);
    783             int i = 0;
    784             try {
    785                 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
    786                 AttributeSet attrs = Xml.asAttributeSet(parser);
    787                 beginDocument(parser, TAG_FAVORITES);
    788 
    789                 final int depth = parser.getDepth();
    790 
    791                 int type;
    792                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
    793                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    794 
    795                     if (type != XmlPullParser.START_TAG) {
    796                         continue;
    797                     }
    798 
    799                     boolean added = false;
    800                     final String name = parser.getName();
    801 
    802                     TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
    803 
    804                     long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
    805                     if (a.hasValue(R.styleable.Favorite_container)) {
    806                         container = Long.valueOf(a.getString(R.styleable.Favorite_container));
    807                     }
    808 
    809                     String screen = a.getString(R.styleable.Favorite_screen);
    810                     String x = a.getString(R.styleable.Favorite_x);
    811                     String y = a.getString(R.styleable.Favorite_y);
    812 
    813                     // If we are adding to the hotseat, the screen is used as the position in the
    814                     // hotseat. This screen can't be at position 0 because AllApps is in the
    815                     // zeroth position.
    816                     if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
    817                             && Integer.valueOf(screen) == allAppsButtonRank) {
    818                         throw new RuntimeException("Invalid screen position for hotseat item");
    819                     }
    820 
    821                     values.clear();
    822                     values.put(LauncherSettings.Favorites.CONTAINER, container);
    823                     values.put(LauncherSettings.Favorites.SCREEN, screen);
    824                     values.put(LauncherSettings.Favorites.CELLX, x);
    825                     values.put(LauncherSettings.Favorites.CELLY, y);
    826 
    827                     if (TAG_FAVORITE.equals(name)) {
    828                         long id = addAppShortcut(db, values, a, packageManager, intent);
    829                         added = id >= 0;
    830                     } else if (TAG_SEARCH.equals(name)) {
    831                         added = addSearchWidget(db, values);
    832                     } else if (TAG_CLOCK.equals(name)) {
    833                         added = addClockWidget(db, values);
    834                     } else if (TAG_APPWIDGET.equals(name)) {
    835                         added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
    836                     } else if (TAG_SHORTCUT.equals(name)) {
    837                         long id = addUriShortcut(db, values, a);
    838                         added = id >= 0;
    839                     } else if (TAG_FOLDER.equals(name)) {
    840                         String title;
    841                         int titleResId =  a.getResourceId(R.styleable.Favorite_title, -1);
    842                         if (titleResId != -1) {
    843                             title = mContext.getResources().getString(titleResId);
    844                         } else {
    845                             title = mContext.getResources().getString(R.string.folder_name);
    846                         }
    847                         values.put(LauncherSettings.Favorites.TITLE, title);
    848                         long folderId = addFolder(db, values);
    849                         added = folderId >= 0;
    850 
    851                         ArrayList<Long> folderItems = new ArrayList<Long>();
    852 
    853                         int folderDepth = parser.getDepth();
    854                         while ((type = parser.next()) != XmlPullParser.END_TAG ||
    855                                 parser.getDepth() > folderDepth) {
    856                             if (type != XmlPullParser.START_TAG) {
    857                                 continue;
    858                             }
    859                             final String folder_item_name = parser.getName();
    860 
    861                             TypedArray ar = mContext.obtainStyledAttributes(attrs,
    862                                     R.styleable.Favorite);
    863                             values.clear();
    864                             values.put(LauncherSettings.Favorites.CONTAINER, folderId);
    865 
    866                             if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
    867                                 long id =
    868                                     addAppShortcut(db, values, ar, packageManager, intent);
    869                                 if (id >= 0) {
    870                                     folderItems.add(id);
    871                                 }
    872                             } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
    873                                 long id = addUriShortcut(db, values, ar);
    874                                 if (id >= 0) {
    875                                     folderItems.add(id);
    876                                 }
    877                             } else {
    878                                 throw new RuntimeException("Folders can " +
    879                                         "contain only shortcuts");
    880                             }
    881                             ar.recycle();
    882                         }
    883                         // We can only have folders with >= 2 items, so we need to remove the
    884                         // folder and clean up if less than 2 items were included, or some
    885                         // failed to add, and less than 2 were actually added
    886                         if (folderItems.size() < 2 && folderId >= 0) {
    887                             // We just delete the folder and any items that made it
    888                             deleteId(db, folderId);
    889                             if (folderItems.size() > 0) {
    890                                 deleteId(db, folderItems.get(0));
    891                             }
    892                             added = false;
    893                         }
    894                     }
    895                     if (added) i++;
    896                     a.recycle();
    897                 }
    898             } catch (XmlPullParserException e) {
    899                 Log.w(TAG, "Got exception parsing favorites.", e);
    900             } catch (IOException e) {
    901                 Log.w(TAG, "Got exception parsing favorites.", e);
    902             } catch (RuntimeException e) {
    903                 Log.w(TAG, "Got exception parsing favorites.", e);
    904             }
    905 
    906             return i;
    907         }
    908 
    909         private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
    910                 PackageManager packageManager, Intent intent) {
    911             long id = -1;
    912             ActivityInfo info;
    913             String packageName = a.getString(R.styleable.Favorite_packageName);
    914             String className = a.getString(R.styleable.Favorite_className);
    915             try {
    916                 ComponentName cn;
    917                 try {
    918                     cn = new ComponentName(packageName, className);
    919                     info = packageManager.getActivityInfo(cn, 0);
    920                 } catch (PackageManager.NameNotFoundException nnfe) {
    921                     String[] packages = packageManager.currentToCanonicalPackageNames(
    922                         new String[] { packageName });
    923                     cn = new ComponentName(packages[0], className);
    924                     info = packageManager.getActivityInfo(cn, 0);
    925                 }
    926                 id = generateNewId();
    927                 intent.setComponent(cn);
    928                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    929                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    930                 values.put(Favorites.INTENT, intent.toUri(0));
    931                 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
    932                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
    933                 values.put(Favorites.SPANX, 1);
    934                 values.put(Favorites.SPANY, 1);
    935                 values.put(Favorites._ID, generateNewId());
    936                 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
    937                     return -1;
    938                 }
    939             } catch (PackageManager.NameNotFoundException e) {
    940                 Log.w(TAG, "Unable to add favorite: " + packageName +
    941                         "/" + className, e);
    942             }
    943             return id;
    944         }
    945 
    946         private long addFolder(SQLiteDatabase db, ContentValues values) {
    947             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
    948             values.put(Favorites.SPANX, 1);
    949             values.put(Favorites.SPANY, 1);
    950             long id = generateNewId();
    951             values.put(Favorites._ID, id);
    952             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
    953                 return -1;
    954             } else {
    955                 return id;
    956             }
    957         }
    958 
    959         private ComponentName getSearchWidgetProvider() {
    960             SearchManager searchManager =
    961                     (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
    962             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
    963             if (searchComponent == null) return null;
    964             return getProviderInPackage(searchComponent.getPackageName());
    965         }
    966 
    967         /**
    968          * Gets an appwidget provider from the given package. If the package contains more than
    969          * one appwidget provider, an arbitrary one is returned.
    970          */
    971         private ComponentName getProviderInPackage(String packageName) {
    972             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    973             List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
    974             if (providers == null) return null;
    975             final int providerCount = providers.size();
    976             for (int i = 0; i < providerCount; i++) {
    977                 ComponentName provider = providers.get(i).provider;
    978                 if (provider != null && provider.getPackageName().equals(packageName)) {
    979                     return provider;
    980                 }
    981             }
    982             return null;
    983         }
    984 
    985         private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
    986             ComponentName cn = getSearchWidgetProvider();
    987             return addAppWidget(db, values, cn, 4, 1, null);
    988         }
    989 
    990         private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
    991             ComponentName cn = new ComponentName("com.android.alarmclock",
    992                     "com.android.alarmclock.AnalogAppWidgetProvider");
    993             return addAppWidget(db, values, cn, 2, 2, null);
    994         }
    995 
    996         private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
    997                 SQLiteDatabase db, ContentValues values, TypedArray a,
    998                 PackageManager packageManager) throws XmlPullParserException, IOException {
    999 
   1000             String packageName = a.getString(R.styleable.Favorite_packageName);
   1001             String className = a.getString(R.styleable.Favorite_className);
   1002 
   1003             if (packageName == null || className == null) {
   1004                 return false;
   1005             }
   1006 
   1007             boolean hasPackage = true;
   1008             ComponentName cn = new ComponentName(packageName, className);
   1009             try {
   1010                 packageManager.getReceiverInfo(cn, 0);
   1011             } catch (Exception e) {
   1012                 String[] packages = packageManager.currentToCanonicalPackageNames(
   1013                         new String[] { packageName });
   1014                 cn = new ComponentName(packages[0], className);
   1015                 try {
   1016                     packageManager.getReceiverInfo(cn, 0);
   1017                 } catch (Exception e1) {
   1018                     hasPackage = false;
   1019                 }
   1020             }
   1021 
   1022             if (hasPackage) {
   1023                 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
   1024                 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
   1025 
   1026                 // Read the extras
   1027                 Bundle extras = new Bundle();
   1028                 int widgetDepth = parser.getDepth();
   1029                 while ((type = parser.next()) != XmlPullParser.END_TAG ||
   1030                         parser.getDepth() > widgetDepth) {
   1031                     if (type != XmlPullParser.START_TAG) {
   1032                         continue;
   1033                     }
   1034 
   1035                     TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
   1036                     if (TAG_EXTRA.equals(parser.getName())) {
   1037                         String key = ar.getString(R.styleable.Extra_key);
   1038                         String value = ar.getString(R.styleable.Extra_value);
   1039                         if (key != null && value != null) {
   1040                             extras.putString(key, value);
   1041                         } else {
   1042                             throw new RuntimeException("Widget extras must have a key and value");
   1043                         }
   1044                     } else {
   1045                         throw new RuntimeException("Widgets can contain only extras");
   1046                     }
   1047                     ar.recycle();
   1048                 }
   1049 
   1050                 return addAppWidget(db, values, cn, spanX, spanY, extras);
   1051             }
   1052 
   1053             return false;
   1054         }
   1055 
   1056         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
   1057                 int spanX, int spanY, Bundle extras) {
   1058             boolean allocatedAppWidgets = false;
   1059             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
   1060 
   1061             try {
   1062                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
   1063 
   1064                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
   1065                 values.put(Favorites.SPANX, spanX);
   1066                 values.put(Favorites.SPANY, spanY);
   1067                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
   1068                 values.put(Favorites._ID, generateNewId());
   1069                 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
   1070 
   1071                 allocatedAppWidgets = true;
   1072 
   1073                 // TODO: need to check return value
   1074                 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
   1075 
   1076                 // Send a broadcast to configure the widget
   1077                 if (extras != null && !extras.isEmpty()) {
   1078                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
   1079                     intent.setComponent(cn);
   1080                     intent.putExtras(extras);
   1081                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
   1082                     mContext.sendBroadcast(intent);
   1083                 }
   1084             } catch (RuntimeException ex) {
   1085                 Log.e(TAG, "Problem allocating appWidgetId", ex);
   1086             }
   1087 
   1088             return allocatedAppWidgets;
   1089         }
   1090 
   1091         private long addUriShortcut(SQLiteDatabase db, ContentValues values,
   1092                 TypedArray a) {
   1093             Resources r = mContext.getResources();
   1094 
   1095             final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
   1096             final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
   1097 
   1098             Intent intent;
   1099             String uri = null;
   1100             try {
   1101                 uri = a.getString(R.styleable.Favorite_uri);
   1102                 intent = Intent.parseUri(uri, 0);
   1103             } catch (URISyntaxException e) {
   1104                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
   1105                 return -1; // Oh well
   1106             }
   1107 
   1108             if (iconResId == 0 || titleResId == 0) {
   1109                 Log.w(TAG, "Shortcut is missing title or icon resource ID");
   1110                 return -1;
   1111             }
   1112 
   1113             long id = generateNewId();
   1114             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1115             values.put(Favorites.INTENT, intent.toUri(0));
   1116             values.put(Favorites.TITLE, r.getString(titleResId));
   1117             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
   1118             values.put(Favorites.SPANX, 1);
   1119             values.put(Favorites.SPANY, 1);
   1120             values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
   1121             values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
   1122             values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
   1123             values.put(Favorites._ID, id);
   1124 
   1125             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
   1126                 return -1;
   1127             }
   1128             return id;
   1129         }
   1130     }
   1131 
   1132     /**
   1133      * Build a query string that will match any row where the column matches
   1134      * anything in the values list.
   1135      */
   1136     static String buildOrWhereString(String column, int[] values) {
   1137         StringBuilder selectWhere = new StringBuilder();
   1138         for (int i = values.length - 1; i >= 0; i--) {
   1139             selectWhere.append(column).append("=").append(values[i]);
   1140             if (i > 0) {
   1141                 selectWhere.append(" OR ");
   1142             }
   1143         }
   1144         return selectWhere.toString();
   1145     }
   1146 
   1147     static class SqlArguments {
   1148         public final String table;
   1149         public final String where;
   1150         public final String[] args;
   1151 
   1152         SqlArguments(Uri url, String where, String[] args) {
   1153             if (url.getPathSegments().size() == 1) {
   1154                 this.table = url.getPathSegments().get(0);
   1155                 this.where = where;
   1156                 this.args = args;
   1157             } else if (url.getPathSegments().size() != 2) {
   1158                 throw new IllegalArgumentException("Invalid URI: " + url);
   1159             } else if (!TextUtils.isEmpty(where)) {
   1160                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
   1161             } else {
   1162                 this.table = url.getPathSegments().get(0);
   1163                 this.where = "_id=" + ContentUris.parseId(url);
   1164                 this.args = null;
   1165             }
   1166         }
   1167 
   1168         SqlArguments(Uri url) {
   1169             if (url.getPathSegments().size() == 1) {
   1170                 table = url.getPathSegments().get(0);
   1171                 where = null;
   1172                 args = null;
   1173             } else {
   1174                 throw new IllegalArgumentException("Invalid URI: " + url);
   1175             }
   1176         }
   1177     }
   1178 }
   1179