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