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