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