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