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