• 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.launcher;
18 
19 import android.appwidget.AppWidgetHost;
20 import android.content.ContentProvider;
21 import android.content.Context;
22 import android.content.ContentValues;
23 import android.content.Intent;
24 import android.content.ComponentName;
25 import android.content.ContentUris;
26 import android.content.ContentResolver;
27 import android.content.res.XmlResourceParser;
28 import android.content.res.TypedArray;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ActivityInfo;
31 import android.database.sqlite.SQLiteOpenHelper;
32 import android.database.sqlite.SQLiteDatabase;
33 import android.database.sqlite.SQLiteQueryBuilder;
34 import android.database.Cursor;
35 import android.database.SQLException;
36 import android.util.Log;
37 import android.util.Xml;
38 import android.util.AttributeSet;
39 import android.net.Uri;
40 import android.text.TextUtils;
41 import android.os.*;
42 import android.provider.Settings;
43 
44 import java.io.IOException;
45 import java.util.ArrayList;
46 
47 import org.xmlpull.v1.XmlPullParserException;
48 import org.xmlpull.v1.XmlPullParser;
49 import com.android.internal.util.XmlUtils;
50 import com.android.launcher.LauncherSettings.Favorites;
51 
52 public class LauncherProvider extends ContentProvider {
53     private static final String LOG_TAG = "LauncherProvider";
54     private static final boolean LOGD = true;
55 
56     private static final String DATABASE_NAME = "launcher.db";
57 
58     private static final int DATABASE_VERSION = 4;
59 
60     static final String AUTHORITY = "com.android.launcher.settings";
61 
62     static final String EXTRA_BIND_SOURCES = "com.android.launcher.settings.bindsources";
63     static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets";
64 
65     static final String TABLE_FAVORITES = "favorites";
66     static final String TABLE_GESTURES = "gestures";
67     static final String PARAMETER_NOTIFY = "notify";
68 
69     /**
70      * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
71      * {@link AppWidgetHost#deleteHost()} is called during database creation.
72      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
73      */
74     static final Uri CONTENT_APPWIDGET_RESET_URI =
75             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
76 
77     private SQLiteOpenHelper mOpenHelper;
78 
79     @Override
onCreate()80     public boolean onCreate() {
81         mOpenHelper = new DatabaseHelper(getContext());
82         return true;
83     }
84 
85     @Override
getType(Uri uri)86     public String getType(Uri uri) {
87         SqlArguments args = new SqlArguments(uri, null, null);
88         if (TextUtils.isEmpty(args.where)) {
89             return "vnd.android.cursor.dir/" + args.table;
90         } else {
91             return "vnd.android.cursor.item/" + args.table;
92         }
93     }
94 
95     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)96     public Cursor query(Uri uri, String[] projection, String selection,
97             String[] selectionArgs, String sortOrder) {
98 
99         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
100         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
101         qb.setTables(args.table);
102 
103         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
104         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
105         result.setNotificationUri(getContext().getContentResolver(), uri);
106 
107         return result;
108     }
109 
110     @Override
insert(Uri uri, ContentValues initialValues)111     public Uri insert(Uri uri, ContentValues initialValues) {
112         SqlArguments args = new SqlArguments(uri);
113 
114         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
115         final long rowId = db.insert(args.table, null, initialValues);
116         if (rowId <= 0) return null;
117 
118         uri = ContentUris.withAppendedId(uri, rowId);
119         sendNotify(uri);
120 
121         return uri;
122     }
123 
124     @Override
bulkInsert(Uri uri, ContentValues[] values)125     public int bulkInsert(Uri uri, ContentValues[] values) {
126         SqlArguments args = new SqlArguments(uri);
127 
128         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
129         db.beginTransaction();
130         try {
131             int numValues = values.length;
132             for (int i = 0; i < numValues; i++) {
133                 if (db.insert(args.table, null, values[i]) < 0) return 0;
134             }
135             db.setTransactionSuccessful();
136         } finally {
137             db.endTransaction();
138         }
139 
140         sendNotify(uri);
141         return values.length;
142     }
143 
144     @Override
delete(Uri uri, String selection, String[] selectionArgs)145     public int delete(Uri uri, String selection, String[] selectionArgs) {
146         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
147 
148         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
149         int count = db.delete(args.table, args.where, args.args);
150         if (count > 0) sendNotify(uri);
151 
152         return count;
153     }
154 
155     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)156     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
157         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
158 
159         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
160         int count = db.update(args.table, values, args.where, args.args);
161         if (count > 0) sendNotify(uri);
162 
163         return count;
164     }
165 
sendNotify(Uri uri)166     private void sendNotify(Uri uri) {
167         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
168         if (notify == null || "true".equals(notify)) {
169             getContext().getContentResolver().notifyChange(uri, null);
170         }
171     }
172 
173     private static class DatabaseHelper extends SQLiteOpenHelper {
174         private static final String TAG_FAVORITES = "favorites";
175         private static final String TAG_FAVORITE = "favorite";
176         private static final String TAG_CLOCK = "clock";
177         private static final String TAG_SEARCH = "search";
178 
179         private final Context mContext;
180         private final AppWidgetHost mAppWidgetHost;
181 
DatabaseHelper(Context context)182         DatabaseHelper(Context context) {
183             super(context, DATABASE_NAME, null, DATABASE_VERSION);
184             mContext = context;
185             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
186         }
187 
188         /**
189          * Send notification that we've deleted the {@link AppWidgetHost},
190          * probably as part of the initial database creation. The receiver may
191          * want to re-call {@link AppWidgetHost#startListening()} to ensure
192          * callbacks are correctly set.
193          */
sendAppWidgetResetNotify()194         private void sendAppWidgetResetNotify() {
195             final ContentResolver resolver = mContext.getContentResolver();
196             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
197         }
198 
199         @Override
onCreate(SQLiteDatabase db)200         public void onCreate(SQLiteDatabase db) {
201             if (LOGD) Log.d(LOG_TAG, "creating new launcher database");
202 
203             db.execSQL("CREATE TABLE favorites (" +
204                     "_id INTEGER PRIMARY KEY," +
205                     "title TEXT," +
206                     "intent TEXT," +
207                     "container INTEGER," +
208                     "screen INTEGER," +
209                     "cellX INTEGER," +
210                     "cellY INTEGER," +
211                     "spanX INTEGER," +
212                     "spanY INTEGER," +
213                     "itemType INTEGER," +
214                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
215                     "isShortcut INTEGER," +
216                     "iconType INTEGER," +
217                     "iconPackage TEXT," +
218                     "iconResource TEXT," +
219                     "icon BLOB," +
220                     "uri TEXT," +
221                     "displayMode INTEGER" +
222                     ");");
223 
224             db.execSQL("CREATE TABLE gestures (" +
225                     "_id INTEGER PRIMARY KEY," +
226                     "title TEXT," +
227                     "intent TEXT," +
228                     "itemType INTEGER," +
229                     "iconType INTEGER," +
230                     "iconPackage TEXT," +
231                     "iconResource TEXT," +
232                     "icon BLOB" +
233                     ");");
234 
235             // Database was just created, so wipe any previous widgets
236             if (mAppWidgetHost != null) {
237                 mAppWidgetHost.deleteHost();
238                 sendAppWidgetResetNotify();
239             }
240 
241             if (!convertDatabase(db)) {
242                 // Populate favorites table with initial favorites
243                 loadFavorites(db);
244             }
245         }
246 
convertDatabase(SQLiteDatabase db)247         private boolean convertDatabase(SQLiteDatabase db) {
248             if (LOGD) Log.d(LOG_TAG, "converting database from an older format, but not onUpgrade");
249             boolean converted = false;
250 
251             final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
252                     "/old_favorites?notify=true");
253             final ContentResolver resolver = mContext.getContentResolver();
254             Cursor cursor = null;
255 
256             try {
257                 cursor = resolver.query(uri, null, null, null, null);
258             } catch (Exception e) {
259 	            // Ignore
260             }
261 
262             // We already have a favorites database in the old provider
263             if (cursor != null && cursor.getCount() > 0) {
264                 try {
265                     converted = copyFromCursor(db, cursor) > 0;
266                 } finally {
267                     cursor.close();
268                 }
269 
270                 if (converted) {
271                     resolver.delete(uri, null, null);
272                 }
273             }
274 
275             if (converted) {
276                 // Convert widgets from this import into widgets
277                 if (LOGD) Log.d(LOG_TAG, "converted and now triggering widget upgrade");
278                 convertWidgets(db);
279             }
280 
281             return converted;
282         }
283 
copyFromCursor(SQLiteDatabase db, Cursor c)284         private int copyFromCursor(SQLiteDatabase db, Cursor c) {
285             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
286             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
287             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
288             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
289             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
290             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
291             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
292             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
293             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
294             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
295             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
296             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
297             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
298             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
299 
300             ContentValues[] rows = new ContentValues[c.getCount()];
301             int i = 0;
302             while (c.moveToNext()) {
303                 ContentValues values = new ContentValues(c.getColumnCount());
304                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
305                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
306                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
307                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
308                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
309                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
310                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
311                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
312                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
313                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
314                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
315                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
316                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
317                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
318                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
319                 rows[i++] = values;
320             }
321 
322             db.beginTransaction();
323             int total = 0;
324             try {
325                 int numValues = rows.length;
326                 for (i = 0; i < numValues; i++) {
327                     if (db.insert(TABLE_FAVORITES, null, rows[i]) < 0) {
328                         return 0;
329                     } else {
330                         total++;
331                     }
332                 }
333                 db.setTransactionSuccessful();
334             } finally {
335                 db.endTransaction();
336             }
337 
338             return total;
339         }
340 
341         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)342         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
343             if (LOGD) Log.d(LOG_TAG, "onUpgrade triggered");
344 
345             int version = oldVersion;
346             if (version < 3) {
347                 // upgrade 1,2 -> 3 added appWidgetId column
348                 db.beginTransaction();
349                 try {
350                     // Insert new column for holding appWidgetIds
351                     db.execSQL("ALTER TABLE favorites " +
352                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
353                     db.setTransactionSuccessful();
354                     version = 3;
355                 } catch (SQLException ex) {
356                     // Old version remains, which means we wipe old data
357                     Log.e(LOG_TAG, ex.getMessage(), ex);
358                 } finally {
359                     db.endTransaction();
360                 }
361 
362                 // Convert existing widgets only if table upgrade was successful
363                 if (version == 3) {
364                     convertWidgets(db);
365                 }
366             }
367 
368             if (version < 4) {
369                 db.beginTransaction();
370                 try {
371                     db.execSQL("CREATE TABLE gestures (" +
372                         "_id INTEGER PRIMARY KEY," +
373                         "title TEXT," +
374                         "intent TEXT," +
375                         "itemType INTEGER," +
376                         "iconType INTEGER," +
377                         "iconPackage TEXT," +
378                         "iconResource TEXT," +
379                         "icon BLOB" +
380                         ");");
381                     db.setTransactionSuccessful();
382                     version = 4;
383                 } catch (SQLException ex) {
384                     // Old version remains, which means we wipe old data
385                     Log.e(LOG_TAG, ex.getMessage(), ex);
386                 } finally {
387                     db.endTransaction();
388                 }
389             }
390 
391             if (version != DATABASE_VERSION) {
392                 Log.w(LOG_TAG, "Destroying all old data.");
393                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
394                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_GESTURES);
395                 onCreate(db);
396             }
397         }
398 
399         /**
400          * Upgrade existing clock and photo frame widgets into their new widget
401          * equivalents. This method allocates appWidgetIds, and then hands off to
402          * LauncherAppWidgetBinder to finish the actual binding.
403          */
convertWidgets(SQLiteDatabase db)404         private void convertWidgets(SQLiteDatabase db) {
405             final int[] bindSources = new int[] {
406                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
407                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
408             };
409 
410             final ArrayList<ComponentName> bindTargets = new ArrayList<ComponentName>();
411             bindTargets.add(new ComponentName("com.android.alarmclock",
412                     "com.android.alarmclock.AnalogAppWidgetProvider"));
413             bindTargets.add(new ComponentName("com.android.camera",
414                     "com.android.camera.PhotoAppWidgetProvider"));
415 
416             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
417 
418             Cursor c = null;
419             boolean allocatedAppWidgets = false;
420 
421             db.beginTransaction();
422             try {
423                 // Select and iterate through each matching widget
424                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID },
425                         selectWhere, null, null, null, null);
426 
427                 if (LOGD) Log.d(LOG_TAG, "found upgrade cursor count="+c.getCount());
428 
429                 final ContentValues values = new ContentValues();
430                 while (c != null && c.moveToNext()) {
431                     long favoriteId = c.getLong(0);
432 
433                     // Allocate and update database with new appWidgetId
434                     try {
435                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
436 
437                         if (LOGD) Log.d(LOG_TAG, "allocated appWidgetId="+appWidgetId+" for favoriteId="+favoriteId);
438 
439                         values.clear();
440                         values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
441 
442                         // Original widgets might not have valid spans when upgrading
443                         values.put(LauncherSettings.Favorites.SPANX, 2);
444                         values.put(LauncherSettings.Favorites.SPANY, 2);
445 
446                         String updateWhere = Favorites._ID + "=" + favoriteId;
447                         db.update(TABLE_FAVORITES, values, updateWhere, null);
448 
449                         allocatedAppWidgets = true;
450                     } catch (RuntimeException ex) {
451                         Log.e(LOG_TAG, "Problem allocating appWidgetId", ex);
452                     }
453                 }
454 
455                 db.setTransactionSuccessful();
456             } catch (SQLException ex) {
457                 Log.w(LOG_TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
458             } finally {
459                 db.endTransaction();
460                 if (c != null) {
461                     c.close();
462                 }
463             }
464 
465             // If any appWidgetIds allocated, then launch over to binder
466             if (allocatedAppWidgets) {
467                 launchAppWidgetBinder(bindSources, bindTargets);
468             }
469         }
470 
471         /**
472          * Launch the widget binder that walks through the Launcher database,
473          * binding any matching widgets to the corresponding targets. We can't
474          * bind ourselves because our parent process can't obtain the
475          * BIND_APPWIDGET permission.
476          */
launchAppWidgetBinder(int[] bindSources, ArrayList<ComponentName> bindTargets)477         private void launchAppWidgetBinder(int[] bindSources, ArrayList<ComponentName> bindTargets) {
478             final Intent intent = new Intent();
479             intent.setComponent(new ComponentName("com.android.settings",
480                     "com.android.settings.LauncherAppWidgetBinder"));
481             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
482 
483             final Bundle extras = new Bundle();
484             extras.putIntArray(EXTRA_BIND_SOURCES, bindSources);
485             extras.putParcelableArrayList(EXTRA_BIND_TARGETS, bindTargets);
486             intent.putExtras(extras);
487 
488             mContext.startActivity(intent);
489         }
490 
491         /**
492          * Loads the default set of favorite packages from an xml file.
493          *
494          * @param db The database to write the values into
495          */
loadFavorites(SQLiteDatabase db)496         private int loadFavorites(SQLiteDatabase db) {
497             Intent intent = new Intent(Intent.ACTION_MAIN, null);
498             intent.addCategory(Intent.CATEGORY_LAUNCHER);
499             ContentValues values = new ContentValues();
500 
501             PackageManager packageManager = mContext.getPackageManager();
502             int i = 0;
503             try {
504                 XmlResourceParser parser = mContext.getResources().getXml(R.xml.default_workspace);
505                 AttributeSet attrs = Xml.asAttributeSet(parser);
506                 XmlUtils.beginDocument(parser, TAG_FAVORITES);
507 
508                 final int depth = parser.getDepth();
509 
510                 int type;
511                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
512                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
513 
514                     if (type != XmlPullParser.START_TAG) {
515                         continue;
516                     }
517 
518                     boolean added = false;
519                     final String name = parser.getName();
520 
521                     TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
522 
523                     values.clear();
524                     values.put(LauncherSettings.Favorites.CONTAINER,
525                             LauncherSettings.Favorites.CONTAINER_DESKTOP);
526                     values.put(LauncherSettings.Favorites.SCREEN,
527                             a.getString(R.styleable.Favorite_screen));
528                     values.put(LauncherSettings.Favorites.CELLX,
529                             a.getString(R.styleable.Favorite_x));
530                     values.put(LauncherSettings.Favorites.CELLY,
531                             a.getString(R.styleable.Favorite_y));
532 
533                     if (TAG_FAVORITE.equals(name)) {
534                         added = addShortcut(db, values, a, packageManager, intent);
535                     } else if (TAG_SEARCH.equals(name)) {
536                         added = addSearchWidget(db, values);
537                     } else if (TAG_CLOCK.equals(name)) {
538                         added = addClockWidget(db, values);
539                     }
540 
541                     if (added) i++;
542 
543                     a.recycle();
544                 }
545             } catch (XmlPullParserException e) {
546                 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
547             } catch (IOException e) {
548                 Log.w(LOG_TAG, "Got exception parsing favorites.", e);
549             }
550 
551             return i;
552         }
553 
addShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, PackageManager packageManager, Intent intent)554         private boolean addShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
555                 PackageManager packageManager, Intent intent) {
556 
557             ActivityInfo info;
558             String packageName = a.getString(R.styleable.Favorite_packageName);
559             String className = a.getString(R.styleable.Favorite_className);
560             try {
561                 ComponentName cn = new ComponentName(packageName, className);
562                 info = packageManager.getActivityInfo(cn, 0);
563                 intent.setComponent(cn);
564                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
565                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
566                 values.put(Favorites.INTENT, intent.toUri(0));
567                 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
568                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
569                 values.put(Favorites.SPANX, 1);
570                 values.put(Favorites.SPANY, 1);
571                 db.insert(TABLE_FAVORITES, null, values);
572             } catch (PackageManager.NameNotFoundException e) {
573                 Log.w(LOG_TAG, "Unable to add favorite: " + packageName +
574                         "/" + className, e);
575                 return false;
576             }
577             return true;
578         }
579 
addSearchWidget(SQLiteDatabase db, ContentValues values)580         private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
581             // Add a search box
582             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_WIDGET_SEARCH);
583             values.put(Favorites.SPANX, 4);
584             values.put(Favorites.SPANY, 1);
585             db.insert(TABLE_FAVORITES, null, values);
586 
587             return true;
588         }
589 
addClockWidget(SQLiteDatabase db, ContentValues values)590         private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
591             final int[] bindSources = new int[] {
592                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
593             };
594 
595             final ArrayList<ComponentName> bindTargets = new ArrayList<ComponentName>();
596             bindTargets.add(new ComponentName("com.android.alarmclock",
597                     "com.android.alarmclock.AnalogAppWidgetProvider"));
598 
599             boolean allocatedAppWidgets = false;
600 
601             // Try binding to an analog clock widget
602             try {
603                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
604 
605                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_WIDGET_CLOCK);
606                 values.put(Favorites.SPANX, 2);
607                 values.put(Favorites.SPANY, 2);
608                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
609                 db.insert(TABLE_FAVORITES, null, values);
610 
611                 allocatedAppWidgets = true;
612             } catch (RuntimeException ex) {
613                 Log.e(LOG_TAG, "Problem allocating appWidgetId", ex);
614             }
615 
616             // If any appWidgetIds allocated, then launch over to binder
617             if (allocatedAppWidgets) {
618                 launchAppWidgetBinder(bindSources, bindTargets);
619             }
620 
621             return allocatedAppWidgets;
622         }
623     }
624 
625     /**
626      * Build a query string that will match any row where the column matches
627      * anything in the values list.
628      */
buildOrWhereString(String column, int[] values)629     static String buildOrWhereString(String column, int[] values) {
630         StringBuilder selectWhere = new StringBuilder();
631         for (int i = values.length - 1; i >= 0; i--) {
632             selectWhere.append(column).append("=").append(values[i]);
633             if (i > 0) {
634                 selectWhere.append(" OR ");
635             }
636         }
637         return selectWhere.toString();
638     }
639 
640     static class SqlArguments {
641         public final String table;
642         public final String where;
643         public final String[] args;
644 
SqlArguments(Uri url, String where, String[] args)645         SqlArguments(Uri url, String where, String[] args) {
646             if (url.getPathSegments().size() == 1) {
647                 this.table = url.getPathSegments().get(0);
648                 this.where = where;
649                 this.args = args;
650             } else if (url.getPathSegments().size() != 2) {
651                 throw new IllegalArgumentException("Invalid URI: " + url);
652             } else if (!TextUtils.isEmpty(where)) {
653                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
654             } else {
655                 this.table = url.getPathSegments().get(0);
656                 this.where = "_id=" + ContentUris.parseId(url);
657                 this.args = null;
658             }
659         }
660 
SqlArguments(Uri url)661         SqlArguments(Uri url) {
662             if (url.getPathSegments().size() == 1) {
663                 table = url.getPathSegments().get(0);
664                 where = null;
665                 args = null;
666             } else {
667                 throw new IllegalArgumentException("Invalid URI: " + url);
668             }
669         }
670     }
671 }
672