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