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