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