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