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