LauncherProvider.java revision 509cd6a656627187dae20fdbec2bad76dfe7fab6
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            // Where's version 5?
370            // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
371            // - Passion shipped on 2.1 with version 6 of launcher2
372            // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
373            //   but version 5 on there was the updateContactsShortcuts change
374            //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
375            // The updateContactsShortcuts change is idempotent, so running it twice
376            // is okay so we'll do that when upgrading the devices that shipped with it.
377            if (version < 6) {
378                // We went from 3 to 5 screens. Move everything 1 to the right
379                db.beginTransaction();
380                try {
381                    db.execSQL("UPDATE favorites SET screen=(screen + 1);");
382                    db.setTransactionSuccessful();
383                } catch (SQLException ex) {
384                    // Old version remains, which means we wipe old data
385                    Log.e(TAG, ex.getMessage(), ex);
386                } finally {
387                    db.endTransaction();
388                }
389
390               // We added the fast track.
391                if (updateContactsShortcuts(db)) {
392                    version = 6;
393                }
394            }
395
396            if (version < 7) {
397                // Version 7 gets rid of the special search widget.
398                convertWidgets(db);
399                version = 7;
400            }
401
402            if (version < 8) {
403                // Version 8 (froyo) has the icons all normalized.  This should
404                // already be the case in practice, but we now rely on it and don't
405                // resample the images each time.
406                normalizeIcons(db);
407                version = 8;
408            }
409
410            if (version != DATABASE_VERSION) {
411                Log.w(TAG, "Destroying all old data.");
412                db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
413                onCreate(db);
414            }
415        }
416
417        private boolean updateContactsShortcuts(SQLiteDatabase db) {
418            Cursor c = null;
419            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
420                    new int[] { Favorites.ITEM_TYPE_SHORTCUT });
421
422            db.beginTransaction();
423            try {
424                // Select and iterate through each matching widget
425                c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.INTENT },
426                        selectWhere, null, null, null, null);
427
428                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
429
430                final ContentValues values = new ContentValues();
431                final int idIndex = c.getColumnIndex(Favorites._ID);
432                final int intentIndex = c.getColumnIndex(Favorites.INTENT);
433
434                while (c != null && c.moveToNext()) {
435                    long favoriteId = c.getLong(idIndex);
436                    final String intentUri = c.getString(intentIndex);
437                    if (intentUri != null) {
438                        try {
439                            Intent intent = Intent.parseUri(intentUri, 0);
440                            android.util.Log.d("Home", intent.toString());
441                            final Uri uri = intent.getData();
442                            final String data = uri.toString();
443                            if (Intent.ACTION_VIEW.equals(intent.getAction()) &&
444                                    (data.startsWith("content://contacts/people/") ||
445                                    data.startsWith("content://com.android.contacts/contacts/lookup/"))) {
446
447                                intent = new Intent("com.android.contacts.action.QUICK_CONTACT");
448                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
449                                        Intent.FLAG_ACTIVITY_CLEAR_TOP |
450                                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
451
452                                intent.setData(uri);
453                                intent.putExtra("mode", 3);
454                                intent.putExtra("exclude_mimes", (String[]) null);
455
456                                values.clear();
457                                values.put(LauncherSettings.Favorites.INTENT, intent.toUri(0));
458
459                                String updateWhere = Favorites._ID + "=" + favoriteId;
460                                db.update(TABLE_FAVORITES, values, updateWhere, null);
461                            }
462                        } catch (RuntimeException ex) {
463                            Log.e(TAG, "Problem upgrading shortcut", ex);
464                        } catch (URISyntaxException e) {
465                            Log.e(TAG, "Problem upgrading shortcut", e);
466                        }
467                    }
468                }
469
470                db.setTransactionSuccessful();
471            } catch (SQLException ex) {
472                Log.w(TAG, "Problem while upgrading contacts", ex);
473                return false;
474            } finally {
475                db.endTransaction();
476                if (c != null) {
477                    c.close();
478                }
479            }
480
481            return true;
482        }
483
484        private void normalizeIcons(SQLiteDatabase db) {
485            Log.d(TAG, "normalizing icons");
486
487            db.beginTransaction();
488            Cursor c = null;
489            SQLiteStatement update = null;
490            try {
491                boolean logged = false;
492                update = db.compileStatement("UPDATE favorites "
493                        + "SET icon=? WHERE _id=?");
494
495                c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
496                        Favorites.ICON_TYPE_BITMAP, null);
497
498                final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
499                final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
500
501                while (c.moveToNext()) {
502                    long id = c.getLong(idIndex);
503                    byte[] data = c.getBlob(iconIndex);
504                    try {
505                        Bitmap bitmap = Utilities.resampleIconBitmap(
506                                BitmapFactory.decodeByteArray(data, 0, data.length),
507                                mContext);
508                        if (bitmap != null) {
509                            update.bindLong(1, id);
510                            data = ItemInfo.flattenBitmap(bitmap);
511                            if (data != null) {
512                                update.bindBlob(2, data);
513                                update.execute();
514                            }
515                            bitmap.recycle();
516                        }
517                    } catch (Exception e) {
518                        if (!logged) {
519                            Log.e(TAG, "Failed normalizing icon " + id, e);
520                        } else {
521                            Log.e(TAG, "Also failed normalizing icon " + id);
522                        }
523                        logged = true;
524                    }
525                }
526                db.setTransactionSuccessful();
527            } catch (SQLException ex) {
528                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
529            } finally {
530                db.endTransaction();
531                if (update != null) {
532                    update.close();
533                }
534                if (c != null) {
535                    c.close();
536                }
537            }
538
539        }
540
541        /**
542         * Upgrade existing clock and photo frame widgets into their new widget
543         * equivalents.
544         */
545        private void convertWidgets(SQLiteDatabase db) {
546            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
547            final int[] bindSources = new int[] {
548                    Favorites.ITEM_TYPE_WIDGET_CLOCK,
549                    Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
550                    Favorites.ITEM_TYPE_WIDGET_SEARCH,
551            };
552
553            final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
554
555            Cursor c = null;
556
557            db.beginTransaction();
558            try {
559                // Select and iterate through each matching widget
560                c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
561                        selectWhere, null, null, null, null);
562
563                if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
564
565                final ContentValues values = new ContentValues();
566                while (c != null && c.moveToNext()) {
567                    long favoriteId = c.getLong(0);
568                    int favoriteType = c.getInt(1);
569
570                    // Allocate and update database with new appWidgetId
571                    try {
572                        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
573
574                        if (LOGD) {
575                            Log.d(TAG, "allocated appWidgetId=" + appWidgetId
576                                    + " for favoriteId=" + favoriteId);
577                        }
578                        values.clear();
579                        values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
580                        values.put(Favorites.APPWIDGET_ID, appWidgetId);
581
582                        // Original widgets might not have valid spans when upgrading
583                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
584                            values.put(LauncherSettings.Favorites.SPANX, 4);
585                            values.put(LauncherSettings.Favorites.SPANY, 1);
586                        } else {
587                            values.put(LauncherSettings.Favorites.SPANX, 2);
588                            values.put(LauncherSettings.Favorites.SPANY, 2);
589                        }
590
591                        String updateWhere = Favorites._ID + "=" + favoriteId;
592                        db.update(TABLE_FAVORITES, values, updateWhere, null);
593
594                        if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
595                            appWidgetManager.bindAppWidgetId(appWidgetId,
596                                    new ComponentName("com.android.alarmclock",
597                                    "com.android.alarmclock.AnalogAppWidgetProvider"));
598                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
599                            appWidgetManager.bindAppWidgetId(appWidgetId,
600                                    new ComponentName("com.android.camera",
601                                    "com.android.camera.PhotoAppWidgetProvider"));
602                        } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
603                            appWidgetManager.bindAppWidgetId(appWidgetId,
604                                    getSearchWidgetProvider());
605                        }
606                    } catch (RuntimeException ex) {
607                        Log.e(TAG, "Problem allocating appWidgetId", ex);
608                    }
609                }
610
611                db.setTransactionSuccessful();
612            } catch (SQLException ex) {
613                Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
614            } finally {
615                db.endTransaction();
616                if (c != null) {
617                    c.close();
618                }
619            }
620        }
621
622        /**
623         * Loads the default set of favorite packages from an xml file.
624         *
625         * @param db The database to write the values into
626         */
627        private int loadFavorites(SQLiteDatabase db) {
628            Intent intent = new Intent(Intent.ACTION_MAIN, null);
629            intent.addCategory(Intent.CATEGORY_LAUNCHER);
630            ContentValues values = new ContentValues();
631
632            PackageManager packageManager = mContext.getPackageManager();
633            int i = 0;
634            try {
635                XmlResourceParser parser = mContext.getResources().getXml(R.xml.default_workspace);
636                AttributeSet attrs = Xml.asAttributeSet(parser);
637                XmlUtils.beginDocument(parser, TAG_FAVORITES);
638
639                final int depth = parser.getDepth();
640
641                int type;
642                while (((type = parser.next()) != XmlPullParser.END_TAG ||
643                        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
644
645                    if (type != XmlPullParser.START_TAG) {
646                        continue;
647                    }
648
649                    boolean added = false;
650                    final String name = parser.getName();
651
652                    TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
653
654                    values.clear();
655                    values.put(LauncherSettings.Favorites.CONTAINER,
656                            LauncherSettings.Favorites.CONTAINER_DESKTOP);
657                    values.put(LauncherSettings.Favorites.SCREEN,
658                            a.getString(R.styleable.Favorite_screen));
659                    values.put(LauncherSettings.Favorites.CELLX,
660                            a.getString(R.styleable.Favorite_x));
661                    values.put(LauncherSettings.Favorites.CELLY,
662                            a.getString(R.styleable.Favorite_y));
663
664                    if (TAG_FAVORITE.equals(name)) {
665                        added = addAppShortcut(db, values, a, packageManager, intent);
666                    } else if (TAG_SEARCH.equals(name)) {
667                        added = addSearchWidget(db, values);
668                    } else if (TAG_CLOCK.equals(name)) {
669                        added = addClockWidget(db, values);
670                    } else if (TAG_APPWIDGET.equals(name)) {
671                        added = addAppWidget(db, values, a, packageManager);
672                    } else if (TAG_SHORTCUT.equals(name)) {
673                        added = addUriShortcut(db, values, a);
674                    }
675
676                    if (added) i++;
677
678                    a.recycle();
679                }
680            } catch (XmlPullParserException e) {
681                Log.w(TAG, "Got exception parsing favorites.", e);
682            } catch (IOException e) {
683                Log.w(TAG, "Got exception parsing favorites.", e);
684            }
685
686            return i;
687        }
688
689        private boolean addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
690                PackageManager packageManager, Intent intent) {
691
692            ActivityInfo info;
693            String packageName = a.getString(R.styleable.Favorite_packageName);
694            String className = a.getString(R.styleable.Favorite_className);
695            try {
696                ComponentName cn;
697                try {
698                    cn = new ComponentName(packageName, className);
699                    info = packageManager.getActivityInfo(cn, 0);
700                } catch (PackageManager.NameNotFoundException nnfe) {
701                    String[] packages = packageManager.currentToCanonicalPackageNames(
702                        new String[] { packageName });
703                    cn = new ComponentName(packages[0], className);
704                    info = packageManager.getActivityInfo(cn, 0);
705                }
706
707                intent.setComponent(cn);
708                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
709                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
710                values.put(Favorites.INTENT, intent.toUri(0));
711                values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
712                values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
713                values.put(Favorites.SPANX, 1);
714                values.put(Favorites.SPANY, 1);
715                db.insert(TABLE_FAVORITES, null, values);
716            } catch (PackageManager.NameNotFoundException e) {
717                Log.w(TAG, "Unable to add favorite: " + packageName +
718                        "/" + className, e);
719                return false;
720            }
721            return true;
722        }
723
724        private ComponentName getSearchWidgetProvider() {
725            SearchManager searchManager =
726                    (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
727            ComponentName searchComponent = searchManager.getGlobalSearchActivity();
728            if (searchComponent == null) return null;
729            return getProviderInPackage(searchComponent.getPackageName());
730        }
731
732        /**
733         * Gets an appwidget provider from the given package. If the package contains more than
734         * one appwidget provider, an arbitrary one is returned.
735         */
736        private ComponentName getProviderInPackage(String packageName) {
737            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
738            List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
739            if (providers == null) return null;
740            final int providerCount = providers.size();
741            for (int i = 0; i < providerCount; i++) {
742                ComponentName provider = providers.get(i).provider;
743                if (provider != null && provider.getPackageName().equals(packageName)) {
744                    return provider;
745                }
746            }
747            return null;
748        }
749
750        private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
751            ComponentName cn = getSearchWidgetProvider();
752            return addAppWidget(db, values, cn, 4, 1);
753        }
754
755        private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
756            ComponentName cn = new ComponentName("com.android.alarmclock",
757                    "com.android.alarmclock.AnalogAppWidgetProvider");
758            return addAppWidget(db, values, cn, 2, 2);
759        }
760
761        private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,
762                PackageManager packageManager) {
763
764            String packageName = a.getString(R.styleable.Favorite_packageName);
765            String className = a.getString(R.styleable.Favorite_className);
766
767            if (packageName == null || className == null) {
768                return false;
769            }
770
771            boolean hasPackage = true;
772            ComponentName cn = new ComponentName(packageName, className);
773            try {
774                packageManager.getReceiverInfo(cn, 0);
775            } catch (Exception e) {
776                String[] packages = packageManager.currentToCanonicalPackageNames(
777                        new String[] { packageName });
778                cn = new ComponentName(packages[0], className);
779                try {
780                    packageManager.getReceiverInfo(cn, 0);
781                } catch (Exception e1) {
782                    hasPackage = false;
783                }
784            }
785
786            if (hasPackage) {
787                int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
788                int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
789                return addAppWidget(db, values, cn, spanX, spanY);
790            }
791
792            return false;
793        }
794
795        private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
796                int spanX, int spanY) {
797            boolean allocatedAppWidgets = false;
798            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
799
800            try {
801                int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
802
803                values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
804                values.put(Favorites.SPANX, spanX);
805                values.put(Favorites.SPANY, spanY);
806                values.put(Favorites.APPWIDGET_ID, appWidgetId);
807                db.insert(TABLE_FAVORITES, null, values);
808
809                allocatedAppWidgets = true;
810
811                appWidgetManager.bindAppWidgetId(appWidgetId, cn);
812            } catch (RuntimeException ex) {
813                Log.e(TAG, "Problem allocating appWidgetId", ex);
814            }
815
816            return allocatedAppWidgets;
817        }
818
819        private boolean addUriShortcut(SQLiteDatabase db, ContentValues values,
820                TypedArray a) {
821            Resources r = mContext.getResources();
822
823            final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
824            final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
825
826            Intent intent;
827            String uri = null;
828            try {
829                uri = a.getString(R.styleable.Favorite_uri);
830                intent = Intent.parseUri(uri, 0);
831            } catch (URISyntaxException e) {
832                Log.w(TAG, "Shortcut has malformed uri: " + uri);
833                return false; // Oh well
834            }
835
836            if (iconResId == 0 || titleResId == 0) {
837                Log.w(TAG, "Shortcut is missing title or icon resource ID");
838                return false;
839            }
840
841            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
842            values.put(Favorites.INTENT, intent.toUri(0));
843            values.put(Favorites.TITLE, r.getString(titleResId));
844            values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
845            values.put(Favorites.SPANX, 1);
846            values.put(Favorites.SPANY, 1);
847            values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
848            values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
849            values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
850
851            db.insert(TABLE_FAVORITES, null, values);
852
853            return true;
854        }
855    }
856
857    /**
858     * Build a query string that will match any row where the column matches
859     * anything in the values list.
860     */
861    static String buildOrWhereString(String column, int[] values) {
862        StringBuilder selectWhere = new StringBuilder();
863        for (int i = values.length - 1; i >= 0; i--) {
864            selectWhere.append(column).append("=").append(values[i]);
865            if (i > 0) {
866                selectWhere.append(" OR ");
867            }
868        }
869        return selectWhere.toString();
870    }
871
872    static class SqlArguments {
873        public final String table;
874        public final String where;
875        public final String[] args;
876
877        SqlArguments(Uri url, String where, String[] args) {
878            if (url.getPathSegments().size() == 1) {
879                this.table = url.getPathSegments().get(0);
880                this.where = where;
881                this.args = args;
882            } else if (url.getPathSegments().size() != 2) {
883                throw new IllegalArgumentException("Invalid URI: " + url);
884            } else if (!TextUtils.isEmpty(where)) {
885                throw new UnsupportedOperationException("WHERE clause not supported: " + url);
886            } else {
887                this.table = url.getPathSegments().get(0);
888                this.where = "_id=" + ContentUris.parseId(url);
889                this.args = null;
890            }
891        }
892
893        SqlArguments(Uri url) {
894            if (url.getPathSegments().size() == 1) {
895                table = url.getPathSegments().get(0);
896                where = null;
897                args = null;
898            } else {
899                throw new IllegalArgumentException("Invalid URI: " + url);
900            }
901        }
902    }
903}
904