Home | History | Annotate | Download | only in launcher2
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher2;
     18 
     19 import android.app.SearchManager;
     20 import android.appwidget.AppWidgetHost;
     21 import android.appwidget.AppWidgetManager;
     22 import android.appwidget.AppWidgetProviderInfo;
     23 import android.content.ContentProvider;
     24 import android.content.Context;
     25 import android.content.ContentValues;
     26 import android.content.Intent;
     27 import android.content.ComponentName;
     28 import android.content.ContentUris;
     29 import android.content.ContentResolver;
     30 import android.content.res.Resources;
     31 import android.content.res.XmlResourceParser;
     32 import android.content.res.TypedArray;
     33 import android.content.pm.PackageManager;
     34 import android.content.pm.ActivityInfo;
     35 import android.database.sqlite.SQLiteOpenHelper;
     36 import android.database.sqlite.SQLiteDatabase;
     37 import android.database.sqlite.SQLiteStatement;
     38 import android.database.sqlite.SQLiteQueryBuilder;
     39 import android.database.Cursor;
     40 import android.database.SQLException;
     41 import android.graphics.Bitmap;
     42 import android.graphics.BitmapFactory;
     43 import android.util.Log;
     44 import android.util.Xml;
     45 import android.util.AttributeSet;
     46 import android.net.Uri;
     47 import android.text.TextUtils;
     48 import android.provider.Settings;
     49 
     50 import java.io.IOException;
     51 import java.net.URISyntaxException;
     52 import java.util.List;
     53 
     54 import org.xmlpull.v1.XmlPullParserException;
     55 import org.xmlpull.v1.XmlPullParser;
     56 
     57 import com.android.internal.util.XmlUtils;
     58 import com.android.launcher2.LauncherSettings.Favorites;
     59 
     60 import com.android.launcher.R;
     61 
     62 public class LauncherProvider extends ContentProvider {
     63     private static final String TAG = "Launcher.LauncherProvider";
     64     private static final boolean LOGD = false;
     65 
     66     private static final String DATABASE_NAME = "launcher.db";
     67 
     68     private static final int DATABASE_VERSION = 8;
     69 
     70     static final String AUTHORITY = "com.android.launcher2.settings";
     71 
     72     static final String TABLE_FAVORITES = "favorites";
     73     static final String PARAMETER_NOTIFY = "notify";
     74 
     75     /**
     76      * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
     77      * {@link AppWidgetHost#deleteHost()} is called during database creation.
     78      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
     79      */
     80     static final Uri CONTENT_APPWIDGET_RESET_URI =
     81             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
     82 
     83     private SQLiteOpenHelper mOpenHelper;
     84 
     85     @Override
     86     public boolean onCreate() {
     87         mOpenHelper = new DatabaseHelper(getContext());
     88         return true;
     89     }
     90 
     91     @Override
     92     public String getType(Uri uri) {
     93         SqlArguments args = new SqlArguments(uri, null, null);
     94         if (TextUtils.isEmpty(args.where)) {
     95             return "vnd.android.cursor.dir/" + args.table;
     96         } else {
     97             return "vnd.android.cursor.item/" + args.table;
     98         }
     99     }
    100 
    101     @Override
    102     public Cursor query(Uri uri, String[] projection, String selection,
    103             String[] selectionArgs, String sortOrder) {
    104 
    105         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    106         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    107         qb.setTables(args.table);
    108 
    109         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    110         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
    111         result.setNotificationUri(getContext().getContentResolver(), uri);
    112 
    113         return result;
    114     }
    115 
    116     @Override
    117     public Uri insert(Uri uri, ContentValues initialValues) {
    118         SqlArguments args = new SqlArguments(uri);
    119 
    120         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    121         final long rowId = db.insert(args.table, null, initialValues);
    122         if (rowId <= 0) return null;
    123 
    124         uri = ContentUris.withAppendedId(uri, rowId);
    125         sendNotify(uri);
    126 
    127         return uri;
    128     }
    129 
    130     @Override
    131     public int bulkInsert(Uri uri, ContentValues[] values) {
    132         SqlArguments args = new SqlArguments(uri);
    133 
    134         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    135         db.beginTransaction();
    136         try {
    137             int numValues = values.length;
    138             for (int i = 0; i < numValues; i++) {
    139                 if (db.insert(args.table, null, values[i]) < 0) return 0;
    140             }
    141             db.setTransactionSuccessful();
    142         } finally {
    143             db.endTransaction();
    144         }
    145 
    146         sendNotify(uri);
    147         return values.length;
    148     }
    149 
    150     @Override
    151     public int delete(Uri uri, String selection, String[] selectionArgs) {
    152         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    153 
    154         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    155         int count = db.delete(args.table, args.where, args.args);
    156         if (count > 0) sendNotify(uri);
    157 
    158         return count;
    159     }
    160 
    161     @Override
    162     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    163         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
    164 
    165         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    166         int count = db.update(args.table, values, args.where, args.args);
    167         if (count > 0) sendNotify(uri);
    168 
    169         return count;
    170     }
    171 
    172     private void sendNotify(Uri uri) {
    173         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
    174         if (notify == null || "true".equals(notify)) {
    175             getContext().getContentResolver().notifyChange(uri, null);
    176         }
    177     }
    178 
    179     private static class DatabaseHelper extends SQLiteOpenHelper {
    180         private static final String TAG_FAVORITES = "favorites";
    181         private static final String TAG_FAVORITE = "favorite";
    182         private static final String TAG_CLOCK = "clock";
    183         private static final String TAG_SEARCH = "search";
    184         private static final String TAG_APPWIDGET = "appwidget";
    185         private static final String TAG_SHORTCUT = "shortcut";
    186 
    187         private final Context mContext;
    188         private final AppWidgetHost mAppWidgetHost;
    189 
    190         DatabaseHelper(Context context) {
    191             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    192             mContext = context;
    193             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
    194         }
    195 
    196         /**
    197          * Send notification that we've deleted the {@link AppWidgetHost},
    198          * probably as part of the initial database creation. The receiver may
    199          * want to re-call {@link AppWidgetHost#startListening()} to ensure
    200          * callbacks are correctly set.
    201          */
    202         private void sendAppWidgetResetNotify() {
    203             final ContentResolver resolver = mContext.getContentResolver();
    204             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
    205         }
    206 
    207         @Override
    208         public void onCreate(SQLiteDatabase db) {
    209             if (LOGD) Log.d(TAG, "creating new launcher database");
    210 
    211             db.execSQL("CREATE TABLE favorites (" +
    212                     "_id INTEGER PRIMARY KEY," +
    213                     "title TEXT," +
    214                     "intent TEXT," +
    215                     "container INTEGER," +
    216                     "screen INTEGER," +
    217                     "cellX INTEGER," +
    218                     "cellY INTEGER," +
    219                     "spanX INTEGER," +
    220                     "spanY INTEGER," +
    221                     "itemType INTEGER," +
    222                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
    223                     "isShortcut INTEGER," +
    224                     "iconType INTEGER," +
    225                     "iconPackage TEXT," +
    226                     "iconResource TEXT," +
    227                     "icon BLOB," +
    228                     "uri TEXT," +
    229                     "displayMode INTEGER" +
    230                     ");");
    231 
    232             // Database was just created, so wipe any previous widgets
    233             if (mAppWidgetHost != null) {
    234                 mAppWidgetHost.deleteHost();
    235                 sendAppWidgetResetNotify();
    236             }
    237 
    238             if (!convertDatabase(db)) {
    239                 // Populate favorites table with initial favorites
    240                 loadFavorites(db);
    241             }
    242         }
    243 
    244         private boolean convertDatabase(SQLiteDatabase db) {
    245             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
    246             boolean converted = false;
    247 
    248             final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
    249                     "/old_favorites?notify=true");
    250             final ContentResolver resolver = mContext.getContentResolver();
    251             Cursor cursor = null;
    252 
    253             try {
    254                 cursor = resolver.query(uri, null, null, null, null);
    255             } catch (Exception e) {
    256 	            // Ignore
    257             }
    258 
    259             // We already have a favorites database in the old provider
    260             if (cursor != null && cursor.getCount() > 0) {
    261                 try {
    262                     converted = copyFromCursor(db, cursor) > 0;
    263                 } finally {
    264                     cursor.close();
    265                 }
    266 
    267                 if (converted) {
    268                     resolver.delete(uri, null, null);
    269                 }
    270             }
    271 
    272             if (converted) {
    273                 // Convert widgets from this import into widgets
    274                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
    275                 convertWidgets(db);
    276             }
    277 
    278             return converted;
    279         }
    280 
    281         private int copyFromCursor(SQLiteDatabase db, Cursor c) {
    282             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
    283             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
    284             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
    285             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
    286             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
    287             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
    288             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
    289             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
    290             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
    291             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
    292             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
    293             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
    294             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
    295             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
    296 
    297             ContentValues[] rows = new ContentValues[c.getCount()];
    298             int i = 0;
    299             while (c.moveToNext()) {
    300                 ContentValues values = new ContentValues(c.getColumnCount());
    301                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
    302                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
    303                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
    304                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
    305                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
    306                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
    307                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
    308                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
    309                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
    310                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
    311                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
    312                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
    313                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
    314                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
    315                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
    316                 rows[i++] = values;
    317             }
    318 
    319             db.beginTransaction();
    320             int total = 0;
    321             try {
    322                 int numValues = rows.length;
    323                 for (i = 0; i < numValues; i++) {
    324                     if (db.insert(TABLE_FAVORITES, null, rows[i]) < 0) {
    325                         return 0;
    326                     } else {
    327                         total++;
    328                     }
    329                 }
    330                 db.setTransactionSuccessful();
    331             } finally {
    332                 db.endTransaction();
    333             }
    334 
    335             return total;
    336         }
    337 
    338         @Override
    339         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    340             if (LOGD) Log.d(TAG, "onUpgrade triggered");
    341 
    342             int version = oldVersion;
    343             if (version < 3) {
    344                 // upgrade 1,2 -> 3 added appWidgetId column
    345                 db.beginTransaction();
    346                 try {
    347                     // Insert new column for holding appWidgetIds
    348                     db.execSQL("ALTER TABLE favorites " +
    349                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
    350                     db.setTransactionSuccessful();
    351                     version = 3;
    352                 } catch (SQLException ex) {
    353                     // Old version remains, which means we wipe old data
    354                     Log.e(TAG, ex.getMessage(), ex);
    355                 } finally {
    356                     db.endTransaction();
    357                 }
    358 
    359                 // Convert existing widgets only if table upgrade was successful
    360                 if (version == 3) {
    361                     convertWidgets(db);
    362                 }
    363             }
    364 
    365             if (version < 4) {
    366                 version = 4;
    367             }
    368 
    369             // Where's version 5?
    370             // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
    371             // - Passion shipped on 2.1 with version 6 of launcher2
    372             // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
    373             //   but version 5 on there was the updateContactsShortcuts change
    374             //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
    375             // The updateContactsShortcuts change is idempotent, so running it twice
    376             // is okay so we'll do that when upgrading the devices that shipped with it.
    377             if (version < 6) {
    378                 // We went from 3 to 5 screens. Move everything 1 to the right
    379                 db.beginTransaction();
    380                 try {
    381                     db.execSQL("UPDATE favorites SET screen=(screen + 1);");
    382                     db.setTransactionSuccessful();
    383                 } catch (SQLException ex) {
    384                     // Old version remains, which means we wipe old data
    385                     Log.e(TAG, ex.getMessage(), ex);
    386                 } finally {
    387                     db.endTransaction();
    388                 }
    389 
    390                // We added the fast track.
    391                 if (updateContactsShortcuts(db)) {
    392                     version = 6;
    393                 }
    394             }
    395 
    396             if (version < 7) {
    397                 // Version 7 gets rid of the special search widget.
    398                 convertWidgets(db);
    399                 version = 7;
    400             }
    401 
    402             if (version < 8) {
    403                 // Version 8 (froyo) has the icons all normalized.  This should
    404                 // already be the case in practice, but we now rely on it and don't
    405                 // resample the images each time.
    406                 normalizeIcons(db);
    407                 version = 8;
    408             }
    409 
    410             if (version != DATABASE_VERSION) {
    411                 Log.w(TAG, "Destroying all old data.");
    412                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
    413                 onCreate(db);
    414             }
    415         }
    416 
    417         private boolean updateContactsShortcuts(SQLiteDatabase db) {
    418             Cursor c = null;
    419             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
    420                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
    421 
    422             db.beginTransaction();
    423             try {
    424                 // Select and iterate through each matching widget
    425                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.INTENT },
    426                         selectWhere, null, null, null, null);
    427 
    428                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
    429 
    430                 final ContentValues values = new ContentValues();
    431                 final int idIndex = c.getColumnIndex(Favorites._ID);
    432                 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
    433 
    434                 while (c != null && c.moveToNext()) {
    435                     long favoriteId = c.getLong(idIndex);
    436                     final String intentUri = c.getString(intentIndex);
    437                     if (intentUri != null) {
    438                         try {
    439                             Intent intent = Intent.parseUri(intentUri, 0);
    440                             android.util.Log.d("Home", intent.toString());
    441                             final Uri uri = intent.getData();
    442                             final String data = uri.toString();
    443                             if (Intent.ACTION_VIEW.equals(intent.getAction()) &&
    444                                     (data.startsWith("content://contacts/people/") ||
    445                                     data.startsWith("content://com.android.contacts/contacts/lookup/"))) {
    446 
    447                                 intent = new Intent("com.android.contacts.action.QUICK_CONTACT");
    448                                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    449                                         Intent.FLAG_ACTIVITY_CLEAR_TOP |
    450                                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    451 
    452                                 intent.setData(uri);
    453                                 intent.putExtra("mode", 3);
    454                                 intent.putExtra("exclude_mimes", (String[]) null);
    455 
    456                                 values.clear();
    457                                 values.put(LauncherSettings.Favorites.INTENT, intent.toUri(0));
    458 
    459                                 String updateWhere = Favorites._ID + "=" + favoriteId;
    460                                 db.update(TABLE_FAVORITES, values, updateWhere, null);
    461                             }
    462                         } catch (RuntimeException ex) {
    463                             Log.e(TAG, "Problem upgrading shortcut", ex);
    464                         } catch (URISyntaxException e) {
    465                             Log.e(TAG, "Problem upgrading shortcut", e);
    466                         }
    467                     }
    468                 }
    469 
    470                 db.setTransactionSuccessful();
    471             } catch (SQLException ex) {
    472                 Log.w(TAG, "Problem while upgrading contacts", ex);
    473                 return false;
    474             } finally {
    475                 db.endTransaction();
    476                 if (c != null) {
    477                     c.close();
    478                 }
    479             }
    480 
    481             return true;
    482         }
    483 
    484         private void normalizeIcons(SQLiteDatabase db) {
    485             Log.d(TAG, "normalizing icons");
    486 
    487             db.beginTransaction();
    488             Cursor c = null;
    489             SQLiteStatement update = null;
    490             try {
    491                 boolean logged = false;
    492                 update = db.compileStatement("UPDATE favorites "
    493                         + "SET icon=? WHERE _id=?");
    494 
    495                 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
    496                         Favorites.ICON_TYPE_BITMAP, null);
    497 
    498                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
    499                 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
    500 
    501                 while (c.moveToNext()) {
    502                     long id = c.getLong(idIndex);
    503                     byte[] data = c.getBlob(iconIndex);
    504                     try {
    505                         Bitmap bitmap = Utilities.resampleIconBitmap(
    506                                 BitmapFactory.decodeByteArray(data, 0, data.length),
    507                                 mContext);
    508                         if (bitmap != null) {
    509                             update.bindLong(1, id);
    510                             data = ItemInfo.flattenBitmap(bitmap);
    511                             if (data != null) {
    512                                 update.bindBlob(2, data);
    513                                 update.execute();
    514                             }
    515                             bitmap.recycle();
    516                         }
    517                     } catch (Exception e) {
    518                         if (!logged) {
    519                             Log.e(TAG, "Failed normalizing icon " + id, e);
    520                         } else {
    521                             Log.e(TAG, "Also failed normalizing icon " + id);
    522                         }
    523                         logged = true;
    524                     }
    525                 }
    526                 db.setTransactionSuccessful();
    527             } catch (SQLException ex) {
    528                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
    529             } finally {
    530                 db.endTransaction();
    531                 if (update != null) {
    532                     update.close();
    533                 }
    534                 if (c != null) {
    535                     c.close();
    536                 }
    537             }
    538 
    539         }
    540 
    541         /**
    542          * Upgrade existing clock and photo frame widgets into their new widget
    543          * equivalents.
    544          */
    545         private void convertWidgets(SQLiteDatabase db) {
    546             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    547             final int[] bindSources = new int[] {
    548                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
    549                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
    550                     Favorites.ITEM_TYPE_WIDGET_SEARCH,
    551             };
    552 
    553             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
    554 
    555             Cursor c = null;
    556 
    557             db.beginTransaction();
    558             try {
    559                 // Select and iterate through each matching widget
    560                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
    561                         selectWhere, null, null, null, null);
    562 
    563                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
    564 
    565                 final ContentValues values = new ContentValues();
    566                 while (c != null && c.moveToNext()) {
    567                     long favoriteId = c.getLong(0);
    568                     int favoriteType = c.getInt(1);
    569 
    570                     // Allocate and update database with new appWidgetId
    571                     try {
    572                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
    573 
    574                         if (LOGD) {
    575                             Log.d(TAG, "allocated appWidgetId=" + appWidgetId
    576                                     + " for favoriteId=" + favoriteId);
    577                         }
    578                         values.clear();
    579                         values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
    580                         values.put(Favorites.APPWIDGET_ID, appWidgetId);
    581 
    582                         // Original widgets might not have valid spans when upgrading
    583                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
    584                             values.put(LauncherSettings.Favorites.SPANX, 4);
    585                             values.put(LauncherSettings.Favorites.SPANY, 1);
    586                         } else {
    587                             values.put(LauncherSettings.Favorites.SPANX, 2);
    588                             values.put(LauncherSettings.Favorites.SPANY, 2);
    589                         }
    590 
    591                         String updateWhere = Favorites._ID + "=" + favoriteId;
    592                         db.update(TABLE_FAVORITES, values, updateWhere, null);
    593 
    594                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
    595                             appWidgetManager.bindAppWidgetId(appWidgetId,
    596                                     new ComponentName("com.android.alarmclock",
    597                                     "com.android.alarmclock.AnalogAppWidgetProvider"));
    598                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
    599                             appWidgetManager.bindAppWidgetId(appWidgetId,
    600                                     new ComponentName("com.android.camera",
    601                                     "com.android.camera.PhotoAppWidgetProvider"));
    602                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
    603                             appWidgetManager.bindAppWidgetId(appWidgetId,
    604                                     getSearchWidgetProvider());
    605                         }
    606                     } catch (RuntimeException ex) {
    607                         Log.e(TAG, "Problem allocating appWidgetId", ex);
    608                     }
    609                 }
    610 
    611                 db.setTransactionSuccessful();
    612             } catch (SQLException ex) {
    613                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
    614             } finally {
    615                 db.endTransaction();
    616                 if (c != null) {
    617                     c.close();
    618                 }
    619             }
    620         }
    621 
    622         /**
    623          * Loads the default set of favorite packages from an xml file.
    624          *
    625          * @param db The database to write the values into
    626          */
    627         private int loadFavorites(SQLiteDatabase db) {
    628             Intent intent = new Intent(Intent.ACTION_MAIN, null);
    629             intent.addCategory(Intent.CATEGORY_LAUNCHER);
    630             ContentValues values = new ContentValues();
    631 
    632             PackageManager packageManager = mContext.getPackageManager();
    633             int i = 0;
    634             try {
    635                 XmlResourceParser parser = mContext.getResources().getXml(R.xml.default_workspace);
    636                 AttributeSet attrs = Xml.asAttributeSet(parser);
    637                 XmlUtils.beginDocument(parser, TAG_FAVORITES);
    638 
    639                 final int depth = parser.getDepth();
    640 
    641                 int type;
    642                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
    643                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    644 
    645                     if (type != XmlPullParser.START_TAG) {
    646                         continue;
    647                     }
    648 
    649                     boolean added = false;
    650                     final String name = parser.getName();
    651 
    652                     TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
    653 
    654                     values.clear();
    655                     values.put(LauncherSettings.Favorites.CONTAINER,
    656                             LauncherSettings.Favorites.CONTAINER_DESKTOP);
    657                     values.put(LauncherSettings.Favorites.SCREEN,
    658                             a.getString(R.styleable.Favorite_screen));
    659                     values.put(LauncherSettings.Favorites.CELLX,
    660                             a.getString(R.styleable.Favorite_x));
    661                     values.put(LauncherSettings.Favorites.CELLY,
    662                             a.getString(R.styleable.Favorite_y));
    663 
    664                     if (TAG_FAVORITE.equals(name)) {
    665                         added = addAppShortcut(db, values, a, packageManager, intent);
    666                     } else if (TAG_SEARCH.equals(name)) {
    667                         added = addSearchWidget(db, values);
    668                     } else if (TAG_CLOCK.equals(name)) {
    669                         added = addClockWidget(db, values);
    670                     } else if (TAG_APPWIDGET.equals(name)) {
    671                         added = addAppWidget(db, values, a, packageManager);
    672                     } else if (TAG_SHORTCUT.equals(name)) {
    673                         added = addUriShortcut(db, values, a);
    674                     }
    675 
    676                     if (added) i++;
    677 
    678                     a.recycle();
    679                 }
    680             } catch (XmlPullParserException e) {
    681                 Log.w(TAG, "Got exception parsing favorites.", e);
    682             } catch (IOException e) {
    683                 Log.w(TAG, "Got exception parsing favorites.", e);
    684             }
    685 
    686             return i;
    687         }
    688 
    689         private boolean addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
    690                 PackageManager packageManager, Intent intent) {
    691 
    692             ActivityInfo info;
    693             String packageName = a.getString(R.styleable.Favorite_packageName);
    694             String className = a.getString(R.styleable.Favorite_className);
    695             try {
    696                 ComponentName cn;
    697                 try {
    698                     cn = new ComponentName(packageName, className);
    699                     info = packageManager.getActivityInfo(cn, 0);
    700                 } catch (PackageManager.NameNotFoundException nnfe) {
    701                     String[] packages = packageManager.currentToCanonicalPackageNames(
    702                         new String[] { packageName });
    703                     cn = new ComponentName(packages[0], className);
    704                     info = packageManager.getActivityInfo(cn, 0);
    705                 }
    706 
    707                 intent.setComponent(cn);
    708                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    709                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    710                 values.put(Favorites.INTENT, intent.toUri(0));
    711                 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
    712                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
    713                 values.put(Favorites.SPANX, 1);
    714                 values.put(Favorites.SPANY, 1);
    715                 db.insert(TABLE_FAVORITES, null, values);
    716             } catch (PackageManager.NameNotFoundException e) {
    717                 Log.w(TAG, "Unable to add favorite: " + packageName +
    718                         "/" + className, e);
    719                 return false;
    720             }
    721             return true;
    722         }
    723 
    724         private ComponentName getSearchWidgetProvider() {
    725             SearchManager searchManager =
    726                     (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
    727             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
    728             if (searchComponent == null) return null;
    729             return getProviderInPackage(searchComponent.getPackageName());
    730         }
    731 
    732         /**
    733          * Gets an appwidget provider from the given package. If the package contains more than
    734          * one appwidget provider, an arbitrary one is returned.
    735          */
    736         private ComponentName getProviderInPackage(String packageName) {
    737             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    738             List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
    739             if (providers == null) return null;
    740             final int providerCount = providers.size();
    741             for (int i = 0; i < providerCount; i++) {
    742                 ComponentName provider = providers.get(i).provider;
    743                 if (provider != null && provider.getPackageName().equals(packageName)) {
    744                     return provider;
    745                 }
    746             }
    747             return null;
    748         }
    749 
    750         private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
    751             ComponentName cn = getSearchWidgetProvider();
    752             return addAppWidget(db, values, cn, 4, 1);
    753         }
    754 
    755         private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
    756             ComponentName cn = new ComponentName("com.android.alarmclock",
    757                     "com.android.alarmclock.AnalogAppWidgetProvider");
    758             return addAppWidget(db, values, cn, 2, 2);
    759         }
    760 
    761         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,
    762                 PackageManager packageManager) {
    763 
    764             String packageName = a.getString(R.styleable.Favorite_packageName);
    765             String className = a.getString(R.styleable.Favorite_className);
    766 
    767             if (packageName == null || className == null) {
    768                 return false;
    769             }
    770 
    771             boolean hasPackage = true;
    772             ComponentName cn = new ComponentName(packageName, className);
    773             try {
    774                 packageManager.getReceiverInfo(cn, 0);
    775             } catch (Exception e) {
    776                 String[] packages = packageManager.currentToCanonicalPackageNames(
    777                         new String[] { packageName });
    778                 cn = new ComponentName(packages[0], className);
    779                 try {
    780                     packageManager.getReceiverInfo(cn, 0);
    781                 } catch (Exception e1) {
    782                     hasPackage = false;
    783                 }
    784             }
    785 
    786             if (hasPackage) {
    787                 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
    788                 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
    789                 return addAppWidget(db, values, cn, spanX, spanY);
    790             }
    791 
    792             return false;
    793         }
    794 
    795         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
    796                 int spanX, int spanY) {
    797             boolean allocatedAppWidgets = false;
    798             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    799 
    800             try {
    801                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
    802 
    803                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
    804                 values.put(Favorites.SPANX, spanX);
    805                 values.put(Favorites.SPANY, spanY);
    806                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
    807                 db.insert(TABLE_FAVORITES, null, values);
    808 
    809                 allocatedAppWidgets = true;
    810 
    811                 appWidgetManager.bindAppWidgetId(appWidgetId, cn);
    812             } catch (RuntimeException ex) {
    813                 Log.e(TAG, "Problem allocating appWidgetId", ex);
    814             }
    815 
    816             return allocatedAppWidgets;
    817         }
    818 
    819         private boolean addUriShortcut(SQLiteDatabase db, ContentValues values,
    820                 TypedArray a) {
    821             Resources r = mContext.getResources();
    822 
    823             final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
    824             final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
    825 
    826             Intent intent;
    827             String uri = null;
    828             try {
    829                 uri = a.getString(R.styleable.Favorite_uri);
    830                 intent = Intent.parseUri(uri, 0);
    831             } catch (URISyntaxException e) {
    832                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
    833                 return false; // Oh well
    834             }
    835 
    836             if (iconResId == 0 || titleResId == 0) {
    837                 Log.w(TAG, "Shortcut is missing title or icon resource ID");
    838                 return false;
    839             }
    840 
    841             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    842             values.put(Favorites.INTENT, intent.toUri(0));
    843             values.put(Favorites.TITLE, r.getString(titleResId));
    844             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
    845             values.put(Favorites.SPANX, 1);
    846             values.put(Favorites.SPANY, 1);
    847             values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
    848             values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
    849             values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
    850 
    851             db.insert(TABLE_FAVORITES, null, values);
    852 
    853             return true;
    854         }
    855     }
    856 
    857     /**
    858      * Build a query string that will match any row where the column matches
    859      * anything in the values list.
    860      */
    861     static String buildOrWhereString(String column, int[] values) {
    862         StringBuilder selectWhere = new StringBuilder();
    863         for (int i = values.length - 1; i >= 0; i--) {
    864             selectWhere.append(column).append("=").append(values[i]);
    865             if (i > 0) {
    866                 selectWhere.append(" OR ");
    867             }
    868         }
    869         return selectWhere.toString();
    870     }
    871 
    872     static class SqlArguments {
    873         public final String table;
    874         public final String where;
    875         public final String[] args;
    876 
    877         SqlArguments(Uri url, String where, String[] args) {
    878             if (url.getPathSegments().size() == 1) {
    879                 this.table = url.getPathSegments().get(0);
    880                 this.where = where;
    881                 this.args = args;
    882             } else if (url.getPathSegments().size() != 2) {
    883                 throw new IllegalArgumentException("Invalid URI: " + url);
    884             } else if (!TextUtils.isEmpty(where)) {
    885                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
    886             } else {
    887                 this.table = url.getPathSegments().get(0);
    888                 this.where = "_id=" + ContentUris.parseId(url);
    889                 this.args = null;
    890             }
    891         }
    892 
    893         SqlArguments(Uri url) {
    894             if (url.getPathSegments().size() == 1) {
    895                 table = url.getPathSegments().get(0);
    896                 where = null;
    897                 args = null;
    898             } else {
    899                 throw new IllegalArgumentException("Invalid URI: " + url);
    900             }
    901         }
    902     }
    903 }
    904