• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher2;
18 
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetHost;
21 import android.appwidget.AppWidgetManager;
22 import android.appwidget.AppWidgetProviderInfo;
23 import android.content.ComponentName;
24 import android.content.ContentProvider;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.res.Resources;
33 import android.content.res.TypedArray;
34 import android.content.res.XmlResourceParser;
35 import android.database.Cursor;
36 import android.database.SQLException;
37 import android.database.sqlite.SQLiteDatabase;
38 import android.database.sqlite.SQLiteOpenHelper;
39 import android.database.sqlite.SQLiteQueryBuilder;
40 import android.database.sqlite.SQLiteStatement;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.net.Uri;
44 import android.provider.Settings;
45 import android.text.TextUtils;
46 import android.util.AttributeSet;
47 import android.util.Log;
48 import android.util.Xml;
49 
50 import com.android.internal.util.XmlUtils;
51 import com.android.launcher.R;
52 import com.android.launcher2.LauncherSettings.Favorites;
53 
54 import org.xmlpull.v1.XmlPullParser;
55 import org.xmlpull.v1.XmlPullParserException;
56 
57 import java.io.IOException;
58 import java.net.URISyntaxException;
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 public 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 = 9;
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 DatabaseHelper mOpenHelper;
84 
85     @Override
onCreate()86     public boolean onCreate() {
87         mOpenHelper = new DatabaseHelper(getContext());
88         ((LauncherApplication) getContext()).setLauncherProvider(this);
89         return true;
90     }
91 
92     @Override
getType(Uri uri)93     public String getType(Uri uri) {
94         SqlArguments args = new SqlArguments(uri, null, null);
95         if (TextUtils.isEmpty(args.where)) {
96             return "vnd.android.cursor.dir/" + args.table;
97         } else {
98             return "vnd.android.cursor.item/" + args.table;
99         }
100     }
101 
102     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)103     public Cursor query(Uri uri, String[] projection, String selection,
104             String[] selectionArgs, String sortOrder) {
105 
106         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
107         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
108         qb.setTables(args.table);
109 
110         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
111         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
112         result.setNotificationUri(getContext().getContentResolver(), uri);
113 
114         return result;
115     }
116 
dbInsertAndCheck(DatabaseHelper helper, SQLiteDatabase db, String table, String nullColumnHack, ContentValues values)117     private static long dbInsertAndCheck(DatabaseHelper helper,
118             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
119         if (!values.containsKey(LauncherSettings.Favorites._ID)) {
120             throw new RuntimeException("Error: attempting to add item without specifying an id");
121         }
122         return db.insert(table, nullColumnHack, values);
123     }
124 
deleteId(SQLiteDatabase db, long id)125     private static void deleteId(SQLiteDatabase db, long id) {
126         Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
127         SqlArguments args = new SqlArguments(uri, null, null);
128         db.delete(args.table, args.where, args.args);
129     }
130 
131     @Override
insert(Uri uri, ContentValues initialValues)132     public Uri insert(Uri uri, ContentValues initialValues) {
133         SqlArguments args = new SqlArguments(uri);
134 
135         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
136         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
137         if (rowId <= 0) return null;
138 
139         uri = ContentUris.withAppendedId(uri, rowId);
140         sendNotify(uri);
141 
142         return uri;
143     }
144 
145     @Override
bulkInsert(Uri uri, ContentValues[] values)146     public int bulkInsert(Uri uri, ContentValues[] values) {
147         SqlArguments args = new SqlArguments(uri);
148 
149         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
150         db.beginTransaction();
151         try {
152             int numValues = values.length;
153             for (int i = 0; i < numValues; i++) {
154                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
155                     return 0;
156                 }
157             }
158             db.setTransactionSuccessful();
159         } finally {
160             db.endTransaction();
161         }
162 
163         sendNotify(uri);
164         return values.length;
165     }
166 
167     @Override
delete(Uri uri, String selection, String[] selectionArgs)168     public int delete(Uri uri, String selection, String[] selectionArgs) {
169         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
170 
171         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
172         int count = db.delete(args.table, args.where, args.args);
173         if (count > 0) sendNotify(uri);
174 
175         return count;
176     }
177 
178     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)179     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
180         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
181 
182         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
183         int count = db.update(args.table, values, args.where, args.args);
184         if (count > 0) sendNotify(uri);
185 
186         return count;
187     }
188 
sendNotify(Uri uri)189     private void sendNotify(Uri uri) {
190         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
191         if (notify == null || "true".equals(notify)) {
192             getContext().getContentResolver().notifyChange(uri, null);
193         }
194     }
195 
generateNewId()196     public long generateNewId() {
197         return mOpenHelper.generateNewId();
198     }
199 
200     private static class DatabaseHelper extends SQLiteOpenHelper {
201         private static final String TAG_FAVORITES = "favorites";
202         private static final String TAG_FAVORITE = "favorite";
203         private static final String TAG_CLOCK = "clock";
204         private static final String TAG_SEARCH = "search";
205         private static final String TAG_APPWIDGET = "appwidget";
206         private static final String TAG_SHORTCUT = "shortcut";
207         private static final String TAG_FOLDER = "folder";
208 
209         private final Context mContext;
210         private final AppWidgetHost mAppWidgetHost;
211         private long mMaxId = -1;
212 
DatabaseHelper(Context context)213         DatabaseHelper(Context context) {
214             super(context, DATABASE_NAME, null, DATABASE_VERSION);
215             mContext = context;
216             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
217 
218             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
219             // the DB here
220             if (mMaxId == -1) {
221                 mMaxId = initializeMaxId(getWritableDatabase());
222             }
223         }
224 
225         /**
226          * Send notification that we've deleted the {@link AppWidgetHost},
227          * probably as part of the initial database creation. The receiver may
228          * want to re-call {@link AppWidgetHost#startListening()} to ensure
229          * callbacks are correctly set.
230          */
sendAppWidgetResetNotify()231         private void sendAppWidgetResetNotify() {
232             final ContentResolver resolver = mContext.getContentResolver();
233             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
234         }
235 
236         @Override
onCreate(SQLiteDatabase db)237         public void onCreate(SQLiteDatabase db) {
238             if (LOGD) Log.d(TAG, "creating new launcher database");
239 
240             mMaxId = 1;
241 
242             db.execSQL("CREATE TABLE favorites (" +
243                     "_id INTEGER PRIMARY KEY," +
244                     "title TEXT," +
245                     "intent TEXT," +
246                     "container INTEGER," +
247                     "screen INTEGER," +
248                     "cellX INTEGER," +
249                     "cellY INTEGER," +
250                     "spanX INTEGER," +
251                     "spanY INTEGER," +
252                     "itemType INTEGER," +
253                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
254                     "isShortcut INTEGER," +
255                     "iconType INTEGER," +
256                     "iconPackage TEXT," +
257                     "iconResource TEXT," +
258                     "icon BLOB," +
259                     "uri TEXT," +
260                     "displayMode INTEGER" +
261                     ");");
262 
263             // Database was just created, so wipe any previous widgets
264             if (mAppWidgetHost != null) {
265                 mAppWidgetHost.deleteHost();
266                 sendAppWidgetResetNotify();
267             }
268 
269             if (!convertDatabase(db)) {
270                 // Populate favorites table with initial favorites
271                 loadFavorites(db, ItemInfo.NO_ID);
272             }
273         }
274 
convertDatabase(SQLiteDatabase db)275         private boolean convertDatabase(SQLiteDatabase db) {
276             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
277             boolean converted = false;
278 
279             final Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
280                     "/old_favorites?notify=true");
281             final ContentResolver resolver = mContext.getContentResolver();
282             Cursor cursor = null;
283 
284             try {
285                 cursor = resolver.query(uri, null, null, null, null);
286             } catch (Exception e) {
287                 // Ignore
288             }
289 
290             // We already have a favorites database in the old provider
291             if (cursor != null && cursor.getCount() > 0) {
292                 try {
293                     converted = copyFromCursor(db, cursor) > 0;
294                 } finally {
295                     cursor.close();
296                 }
297 
298                 if (converted) {
299                     resolver.delete(uri, null, null);
300                 }
301             }
302 
303             if (converted) {
304                 // Convert widgets from this import into widgets
305                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
306                 convertWidgets(db);
307             }
308 
309             return converted;
310         }
311 
copyFromCursor(SQLiteDatabase db, Cursor c)312         private int copyFromCursor(SQLiteDatabase db, Cursor c) {
313             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
314             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
315             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
316             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
317             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
318             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
319             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
320             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
321             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
322             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
323             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
324             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
325             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
326             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
327 
328             ContentValues[] rows = new ContentValues[c.getCount()];
329             int i = 0;
330             while (c.moveToNext()) {
331                 ContentValues values = new ContentValues(c.getColumnCount());
332                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
333                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
334                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
335                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
336                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
337                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
338                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
339                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
340                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
341                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
342                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
343                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
344                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
345                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
346                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
347                 rows[i++] = values;
348             }
349 
350             db.beginTransaction();
351             int total = 0;
352             try {
353                 int numValues = rows.length;
354                 for (i = 0; i < numValues; i++) {
355                     if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
356                         return 0;
357                     } else {
358                         total++;
359                     }
360                 }
361                 db.setTransactionSuccessful();
362             } finally {
363                 db.endTransaction();
364             }
365 
366             return total;
367         }
368 
369         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)370         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
371             if (LOGD) Log.d(TAG, "onUpgrade triggered");
372 
373             int version = oldVersion;
374             if (version < 3) {
375                 // upgrade 1,2 -> 3 added appWidgetId column
376                 db.beginTransaction();
377                 try {
378                     // Insert new column for holding appWidgetIds
379                     db.execSQL("ALTER TABLE favorites " +
380                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
381                     db.setTransactionSuccessful();
382                     version = 3;
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                 // Convert existing widgets only if table upgrade was successful
391                 if (version == 3) {
392                     convertWidgets(db);
393                 }
394             }
395 
396             if (version < 4) {
397                 version = 4;
398             }
399 
400             // Where's version 5?
401             // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
402             // - Passion shipped on 2.1 with version 6 of launcher2
403             // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
404             //   but version 5 on there was the updateContactsShortcuts change
405             //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
406             // The updateContactsShortcuts change is idempotent, so running it twice
407             // is okay so we'll do that when upgrading the devices that shipped with it.
408             if (version < 6) {
409                 // We went from 3 to 5 screens. Move everything 1 to the right
410                 db.beginTransaction();
411                 try {
412                     db.execSQL("UPDATE favorites SET screen=(screen + 1);");
413                     db.setTransactionSuccessful();
414                 } catch (SQLException ex) {
415                     // Old version remains, which means we wipe old data
416                     Log.e(TAG, ex.getMessage(), ex);
417                 } finally {
418                     db.endTransaction();
419                 }
420 
421                // We added the fast track.
422                 if (updateContactsShortcuts(db)) {
423                     version = 6;
424                 }
425             }
426 
427             if (version < 7) {
428                 // Version 7 gets rid of the special search widget.
429                 convertWidgets(db);
430                 version = 7;
431             }
432 
433             if (version < 8) {
434                 // Version 8 (froyo) has the icons all normalized.  This should
435                 // already be the case in practice, but we now rely on it and don't
436                 // resample the images each time.
437                 normalizeIcons(db);
438                 version = 8;
439             }
440 
441             if (version < 9) {
442                 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
443                 // before it gets a change to get set, so we need to read it here when we use it)
444                 if (mMaxId == -1) {
445                     mMaxId = initializeMaxId(db);
446                 }
447 
448                 // Add default hotseat icons
449                 loadFavorites(db, LauncherSettings.Favorites.CONTAINER_HOTSEAT);
450                 version = 9;
451             }
452 
453             if (version != DATABASE_VERSION) {
454                 Log.w(TAG, "Destroying all old data.");
455                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
456                 onCreate(db);
457             }
458         }
459 
updateContactsShortcuts(SQLiteDatabase db)460         private boolean updateContactsShortcuts(SQLiteDatabase db) {
461             Cursor c = null;
462             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
463                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
464 
465             db.beginTransaction();
466             try {
467                 // Select and iterate through each matching widget
468                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.INTENT },
469                         selectWhere, null, null, null, null);
470 
471                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
472 
473                 final ContentValues values = new ContentValues();
474                 final int idIndex = c.getColumnIndex(Favorites._ID);
475                 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
476 
477                 while (c != null && c.moveToNext()) {
478                     long favoriteId = c.getLong(idIndex);
479                     final String intentUri = c.getString(intentIndex);
480                     if (intentUri != null) {
481                         try {
482                             Intent intent = Intent.parseUri(intentUri, 0);
483                             android.util.Log.d("Home", intent.toString());
484                             final Uri uri = intent.getData();
485                             final String data = uri.toString();
486                             if (Intent.ACTION_VIEW.equals(intent.getAction()) &&
487                                     (data.startsWith("content://contacts/people/") ||
488                                     data.startsWith("content://com.android.contacts/contacts/lookup/"))) {
489 
490                                 intent = new Intent("com.android.contacts.action.QUICK_CONTACT");
491                                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
492                                         Intent.FLAG_ACTIVITY_CLEAR_TOP |
493                                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
494 
495                                 intent.setData(uri);
496                                 intent.putExtra("mode", 3);
497                                 intent.putExtra("exclude_mimes", (String[]) null);
498 
499                                 values.clear();
500                                 values.put(LauncherSettings.Favorites.INTENT, intent.toUri(0));
501 
502                                 String updateWhere = Favorites._ID + "=" + favoriteId;
503                                 db.update(TABLE_FAVORITES, values, updateWhere, null);
504                             }
505                         } catch (RuntimeException ex) {
506                             Log.e(TAG, "Problem upgrading shortcut", ex);
507                         } catch (URISyntaxException e) {
508                             Log.e(TAG, "Problem upgrading shortcut", e);
509                         }
510                     }
511                 }
512 
513                 db.setTransactionSuccessful();
514             } catch (SQLException ex) {
515                 Log.w(TAG, "Problem while upgrading contacts", ex);
516                 return false;
517             } finally {
518                 db.endTransaction();
519                 if (c != null) {
520                     c.close();
521                 }
522             }
523 
524             return true;
525         }
526 
normalizeIcons(SQLiteDatabase db)527         private void normalizeIcons(SQLiteDatabase db) {
528             Log.d(TAG, "normalizing icons");
529 
530             db.beginTransaction();
531             Cursor c = null;
532             SQLiteStatement update = null;
533             try {
534                 boolean logged = false;
535                 update = db.compileStatement("UPDATE favorites "
536                         + "SET icon=? WHERE _id=?");
537 
538                 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
539                         Favorites.ICON_TYPE_BITMAP, null);
540 
541                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
542                 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
543 
544                 while (c.moveToNext()) {
545                     long id = c.getLong(idIndex);
546                     byte[] data = c.getBlob(iconIndex);
547                     try {
548                         Bitmap bitmap = Utilities.resampleIconBitmap(
549                                 BitmapFactory.decodeByteArray(data, 0, data.length),
550                                 mContext);
551                         if (bitmap != null) {
552                             update.bindLong(1, id);
553                             data = ItemInfo.flattenBitmap(bitmap);
554                             if (data != null) {
555                                 update.bindBlob(2, data);
556                                 update.execute();
557                             }
558                             bitmap.recycle();
559                         }
560                     } catch (Exception e) {
561                         if (!logged) {
562                             Log.e(TAG, "Failed normalizing icon " + id, e);
563                         } else {
564                             Log.e(TAG, "Also failed normalizing icon " + id);
565                         }
566                         logged = true;
567                     }
568                 }
569                 db.setTransactionSuccessful();
570             } catch (SQLException ex) {
571                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
572             } finally {
573                 db.endTransaction();
574                 if (update != null) {
575                     update.close();
576                 }
577                 if (c != null) {
578                     c.close();
579                 }
580             }
581         }
582 
583         // Generates a new ID to use for an object in your database. This method should be only
584         // called from the main UI thread. As an exception, we do call it when we call the
585         // constructor from the worker thread; however, this doesn't extend until after the
586         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
587         // after that point
generateNewId()588         public long generateNewId() {
589             if (mMaxId < 0) {
590                 throw new RuntimeException("Error: max id was not initialized");
591             }
592             mMaxId += 1;
593             return mMaxId;
594         }
595 
initializeMaxId(SQLiteDatabase db)596         private long initializeMaxId(SQLiteDatabase db) {
597             Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
598 
599             // get the result
600             final int maxIdIndex = 0;
601             long id = -1;
602             if (c != null && c.moveToNext()) {
603                 id = c.getLong(maxIdIndex);
604             }
605             if (c != null) {
606                 c.close();
607             }
608 
609             if (id == -1) {
610                 throw new RuntimeException("Error: could not query max id");
611             }
612 
613             return id;
614         }
615 
616         /**
617          * Upgrade existing clock and photo frame widgets into their new widget
618          * equivalents.
619          */
convertWidgets(SQLiteDatabase db)620         private void convertWidgets(SQLiteDatabase db) {
621             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
622             final int[] bindSources = new int[] {
623                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
624                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
625                     Favorites.ITEM_TYPE_WIDGET_SEARCH,
626             };
627 
628             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
629 
630             Cursor c = null;
631 
632             db.beginTransaction();
633             try {
634                 // Select and iterate through each matching widget
635                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
636                         selectWhere, null, null, null, null);
637 
638                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
639 
640                 final ContentValues values = new ContentValues();
641                 while (c != null && c.moveToNext()) {
642                     long favoriteId = c.getLong(0);
643                     int favoriteType = c.getInt(1);
644 
645                     // Allocate and update database with new appWidgetId
646                     try {
647                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
648 
649                         if (LOGD) {
650                             Log.d(TAG, "allocated appWidgetId=" + appWidgetId
651                                     + " for favoriteId=" + favoriteId);
652                         }
653                         values.clear();
654                         values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
655                         values.put(Favorites.APPWIDGET_ID, appWidgetId);
656 
657                         // Original widgets might not have valid spans when upgrading
658                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
659                             values.put(LauncherSettings.Favorites.SPANX, 4);
660                             values.put(LauncherSettings.Favorites.SPANY, 1);
661                         } else {
662                             values.put(LauncherSettings.Favorites.SPANX, 2);
663                             values.put(LauncherSettings.Favorites.SPANY, 2);
664                         }
665 
666                         String updateWhere = Favorites._ID + "=" + favoriteId;
667                         db.update(TABLE_FAVORITES, values, updateWhere, null);
668 
669                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
670                             appWidgetManager.bindAppWidgetId(appWidgetId,
671                                     new ComponentName("com.android.alarmclock",
672                                     "com.android.alarmclock.AnalogAppWidgetProvider"));
673                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
674                             appWidgetManager.bindAppWidgetId(appWidgetId,
675                                     new ComponentName("com.android.camera",
676                                     "com.android.camera.PhotoAppWidgetProvider"));
677                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
678                             appWidgetManager.bindAppWidgetId(appWidgetId,
679                                     getSearchWidgetProvider());
680                         }
681                     } catch (RuntimeException ex) {
682                         Log.e(TAG, "Problem allocating appWidgetId", ex);
683                     }
684                 }
685 
686                 db.setTransactionSuccessful();
687             } catch (SQLException ex) {
688                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
689             } finally {
690                 db.endTransaction();
691                 if (c != null) {
692                     c.close();
693                 }
694             }
695         }
696 
697         /**
698          * Loads the default set of favorite packages from an xml file.
699          *
700          * @param db The database to write the values into
701          * @param filterContainerId The specific container id of items to load
702          */
loadFavorites(SQLiteDatabase db, long filterContainerId)703         private int loadFavorites(SQLiteDatabase db, long filterContainerId) {
704             Intent intent = new Intent(Intent.ACTION_MAIN, null);
705             intent.addCategory(Intent.CATEGORY_LAUNCHER);
706             ContentValues values = new ContentValues();
707 
708             PackageManager packageManager = mContext.getPackageManager();
709             int i = 0;
710             try {
711                 XmlResourceParser parser = mContext.getResources().getXml(R.xml.default_workspace);
712                 AttributeSet attrs = Xml.asAttributeSet(parser);
713                 XmlUtils.beginDocument(parser, TAG_FAVORITES);
714 
715                 final int depth = parser.getDepth();
716 
717                 int type;
718                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
719                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
720 
721                     if (type != XmlPullParser.START_TAG) {
722                         continue;
723                     }
724 
725                     boolean added = false;
726                     final String name = parser.getName();
727 
728                     TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
729 
730                     long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
731                     if (a.hasValue(R.styleable.Favorite_container)) {
732                         container = Long.valueOf(a.getString(R.styleable.Favorite_container));
733                     }
734                     if (filterContainerId == ItemInfo.NO_ID || filterContainerId == container) {
735                         String screen = a.getString(R.styleable.Favorite_screen);
736                         String x = a.getString(R.styleable.Favorite_x);
737                         String y = a.getString(R.styleable.Favorite_y);
738 
739                         // If we are adding to the hotset, the screen is used as the position in the
740                         // hotset. This screen can't be at position 0 because AllApps is in the
741                         // zeroth position.
742                         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
743                                 Hotseat.isAllAppsButtonRank(Integer.valueOf(screen))) {
744                             throw new RuntimeException("Invalid screen position for hotseat item");
745                         }
746 
747                         values.clear();
748                         values.put(LauncherSettings.Favorites.CONTAINER, container);
749                         values.put(LauncherSettings.Favorites.SCREEN, screen);
750                         values.put(LauncherSettings.Favorites.CELLX, x);
751                         values.put(LauncherSettings.Favorites.CELLY, y);
752 
753                         if (TAG_FAVORITE.equals(name)) {
754                             long id = addAppShortcut(db, values, a, packageManager, intent);
755                             added = id >= 0;
756                         } else if (TAG_SEARCH.equals(name)) {
757                             added = addSearchWidget(db, values);
758                         } else if (TAG_CLOCK.equals(name)) {
759                             added = addClockWidget(db, values);
760                         } else if (TAG_APPWIDGET.equals(name)) {
761                             added = addAppWidget(db, values, a, packageManager);
762                         } else if (TAG_SHORTCUT.equals(name)) {
763                             long id = addUriShortcut(db, values, a);
764                             added = id >= 0;
765                         } else if (TAG_FOLDER.equals(name)) {
766                             String title;
767                             int titleResId =  a.getResourceId(R.styleable.Favorite_title, -1);
768                             if (titleResId != -1) {
769                                 title = mContext.getResources().getString(titleResId);
770                             } else {
771                                 title = mContext.getResources().getString(R.string.folder_name);
772                             }
773                             values.put(LauncherSettings.Favorites.TITLE, title);
774                             long folderId = addFolder(db, values);
775                             added = folderId >= 0;
776 
777                             ArrayList<Long> folderItems = new ArrayList<Long>();
778 
779                             int folderDepth = parser.getDepth();
780                             while ((type = parser.next()) != XmlPullParser.END_TAG ||
781                                     parser.getDepth() > folderDepth) {
782                                 if (type != XmlPullParser.START_TAG) {
783                                     continue;
784                                 }
785                                 final String folder_item_name = parser.getName();
786 
787                                 TypedArray ar = mContext.obtainStyledAttributes(attrs,
788                                         R.styleable.Favorite);
789                                 values.clear();
790                                 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
791 
792                                 if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
793                                     long id =
794                                         addAppShortcut(db, values, ar, packageManager, intent);
795                                     if (id >= 0) {
796                                         folderItems.add(id);
797                                     }
798                                 } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
799                                     long id = addUriShortcut(db, values, ar);
800                                     if (id >= 0) {
801                                         folderItems.add(id);
802                                     }
803                                 } else {
804                                     throw new RuntimeException("Folders can " +
805                                             "contain only shortcuts");
806                                 }
807                                 ar.recycle();
808                             }
809                             // We can only have folders with >= 2 items, so we need to remove the
810                             // folder and clean up if less than 2 items were included, or some
811                             // failed to add, and less than 2 were actually added
812                             if (folderItems.size() < 2 && folderId >= 0) {
813                                 // We just delete the folder and any items that made it
814                                 deleteId(db, folderId);
815                                 if (folderItems.size() > 0) {
816                                     deleteId(db, folderItems.get(0));
817                                 }
818                                 added = false;
819                             }
820                         }
821                     }
822                     if (added) i++;
823                     a.recycle();
824                 }
825             } catch (XmlPullParserException e) {
826                 Log.w(TAG, "Got exception parsing favorites.", e);
827             } catch (IOException e) {
828                 Log.w(TAG, "Got exception parsing favorites.", e);
829             } catch (RuntimeException e) {
830                 Log.w(TAG, "Got exception parsing favorites.", e);
831             }
832 
833             return i;
834         }
835 
addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a, PackageManager packageManager, Intent intent)836         private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
837                 PackageManager packageManager, Intent intent) {
838             long id = -1;
839             ActivityInfo info;
840             String packageName = a.getString(R.styleable.Favorite_packageName);
841             String className = a.getString(R.styleable.Favorite_className);
842             try {
843                 ComponentName cn;
844                 try {
845                     cn = new ComponentName(packageName, className);
846                     info = packageManager.getActivityInfo(cn, 0);
847                 } catch (PackageManager.NameNotFoundException nnfe) {
848                     String[] packages = packageManager.currentToCanonicalPackageNames(
849                         new String[] { packageName });
850                     cn = new ComponentName(packages[0], className);
851                     info = packageManager.getActivityInfo(cn, 0);
852                 }
853                 id = generateNewId();
854                 intent.setComponent(cn);
855                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
856                         Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
857                 values.put(Favorites.INTENT, intent.toUri(0));
858                 values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
859                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
860                 values.put(Favorites.SPANX, 1);
861                 values.put(Favorites.SPANY, 1);
862                 values.put(Favorites._ID, generateNewId());
863                 if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
864                     return -1;
865                 }
866             } catch (PackageManager.NameNotFoundException e) {
867                 Log.w(TAG, "Unable to add favorite: " + packageName +
868                         "/" + className, e);
869             }
870             return id;
871         }
872 
addFolder(SQLiteDatabase db, ContentValues values)873         private long addFolder(SQLiteDatabase db, ContentValues values) {
874             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
875             values.put(Favorites.SPANX, 1);
876             values.put(Favorites.SPANY, 1);
877             long id = generateNewId();
878             values.put(Favorites._ID, id);
879             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
880                 return -1;
881             } else {
882                 return id;
883             }
884         }
885 
getSearchWidgetProvider()886         private ComponentName getSearchWidgetProvider() {
887             SearchManager searchManager =
888                     (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
889             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
890             if (searchComponent == null) return null;
891             return getProviderInPackage(searchComponent.getPackageName());
892         }
893 
894         /**
895          * Gets an appwidget provider from the given package. If the package contains more than
896          * one appwidget provider, an arbitrary one is returned.
897          */
getProviderInPackage(String packageName)898         private ComponentName getProviderInPackage(String packageName) {
899             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
900             List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
901             if (providers == null) return null;
902             final int providerCount = providers.size();
903             for (int i = 0; i < providerCount; i++) {
904                 ComponentName provider = providers.get(i).provider;
905                 if (provider != null && provider.getPackageName().equals(packageName)) {
906                     return provider;
907                 }
908             }
909             return null;
910         }
911 
addSearchWidget(SQLiteDatabase db, ContentValues values)912         private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
913             ComponentName cn = getSearchWidgetProvider();
914             return addAppWidget(db, values, cn, 4, 1);
915         }
916 
addClockWidget(SQLiteDatabase db, ContentValues values)917         private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
918             ComponentName cn = new ComponentName("com.android.alarmclock",
919                     "com.android.alarmclock.AnalogAppWidgetProvider");
920             return addAppWidget(db, values, cn, 2, 2);
921         }
922 
addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a, PackageManager packageManager)923         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, TypedArray a,
924                 PackageManager packageManager) {
925 
926             String packageName = a.getString(R.styleable.Favorite_packageName);
927             String className = a.getString(R.styleable.Favorite_className);
928 
929             if (packageName == null || className == null) {
930                 return false;
931             }
932 
933             boolean hasPackage = true;
934             ComponentName cn = new ComponentName(packageName, className);
935             try {
936                 packageManager.getReceiverInfo(cn, 0);
937             } catch (Exception e) {
938                 String[] packages = packageManager.currentToCanonicalPackageNames(
939                         new String[] { packageName });
940                 cn = new ComponentName(packages[0], className);
941                 try {
942                     packageManager.getReceiverInfo(cn, 0);
943                 } catch (Exception e1) {
944                     hasPackage = false;
945                 }
946             }
947 
948             if (hasPackage) {
949                 int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
950                 int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
951                 return addAppWidget(db, values, cn, spanX, spanY);
952             }
953 
954             return false;
955         }
956 
addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, int spanX, int spanY)957         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
958                 int spanX, int spanY) {
959             boolean allocatedAppWidgets = false;
960             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
961 
962             try {
963                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
964 
965                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
966                 values.put(Favorites.SPANX, spanX);
967                 values.put(Favorites.SPANY, spanY);
968                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
969                 values.put(Favorites._ID, generateNewId());
970                 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
971 
972                 allocatedAppWidgets = true;
973 
974                 appWidgetManager.bindAppWidgetId(appWidgetId, cn);
975             } catch (RuntimeException ex) {
976                 Log.e(TAG, "Problem allocating appWidgetId", ex);
977             }
978 
979             return allocatedAppWidgets;
980         }
981 
addUriShortcut(SQLiteDatabase db, ContentValues values, TypedArray a)982         private long addUriShortcut(SQLiteDatabase db, ContentValues values,
983                 TypedArray a) {
984             Resources r = mContext.getResources();
985 
986             final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
987             final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
988 
989             Intent intent;
990             String uri = null;
991             try {
992                 uri = a.getString(R.styleable.Favorite_uri);
993                 intent = Intent.parseUri(uri, 0);
994             } catch (URISyntaxException e) {
995                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
996                 return -1; // Oh well
997             }
998 
999             if (iconResId == 0 || titleResId == 0) {
1000                 Log.w(TAG, "Shortcut is missing title or icon resource ID");
1001                 return -1;
1002             }
1003 
1004             long id = generateNewId();
1005             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1006             values.put(Favorites.INTENT, intent.toUri(0));
1007             values.put(Favorites.TITLE, r.getString(titleResId));
1008             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1009             values.put(Favorites.SPANX, 1);
1010             values.put(Favorites.SPANY, 1);
1011             values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1012             values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
1013             values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
1014             values.put(Favorites._ID, id);
1015 
1016             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1017                 return -1;
1018             }
1019             return id;
1020         }
1021     }
1022 
1023     /**
1024      * Build a query string that will match any row where the column matches
1025      * anything in the values list.
1026      */
buildOrWhereString(String column, int[] values)1027     static String buildOrWhereString(String column, int[] values) {
1028         StringBuilder selectWhere = new StringBuilder();
1029         for (int i = values.length - 1; i >= 0; i--) {
1030             selectWhere.append(column).append("=").append(values[i]);
1031             if (i > 0) {
1032                 selectWhere.append(" OR ");
1033             }
1034         }
1035         return selectWhere.toString();
1036     }
1037 
1038     static class SqlArguments {
1039         public final String table;
1040         public final String where;
1041         public final String[] args;
1042 
SqlArguments(Uri url, String where, String[] args)1043         SqlArguments(Uri url, String where, String[] args) {
1044             if (url.getPathSegments().size() == 1) {
1045                 this.table = url.getPathSegments().get(0);
1046                 this.where = where;
1047                 this.args = args;
1048             } else if (url.getPathSegments().size() != 2) {
1049                 throw new IllegalArgumentException("Invalid URI: " + url);
1050             } else if (!TextUtils.isEmpty(where)) {
1051                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1052             } else {
1053                 this.table = url.getPathSegments().get(0);
1054                 this.where = "_id=" + ContentUris.parseId(url);
1055                 this.args = null;
1056             }
1057         }
1058 
SqlArguments(Uri url)1059         SqlArguments(Uri url) {
1060             if (url.getPathSegments().size() == 1) {
1061                 table = url.getPathSegments().get(0);
1062                 where = null;
1063                 args = null;
1064             } else {
1065                 throw new IllegalArgumentException("Invalid URI: " + url);
1066             }
1067         }
1068     }
1069 }
1070