• 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.launcher3;
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.ContentProviderOperation;
26 import android.content.ContentProviderResult;
27 import android.content.ContentResolver;
28 import android.content.ContentUris;
29 import android.content.ContentValues;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.OperationApplicationException;
33 import android.content.SharedPreferences;
34 import android.content.pm.ActivityInfo;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.XmlResourceParser;
40 import android.database.Cursor;
41 import android.database.SQLException;
42 import android.database.sqlite.SQLiteDatabase;
43 import android.database.sqlite.SQLiteOpenHelper;
44 import android.database.sqlite.SQLiteQueryBuilder;
45 import android.database.sqlite.SQLiteStatement;
46 import android.graphics.Bitmap;
47 import android.graphics.BitmapFactory;
48 import android.net.Uri;
49 import android.os.Bundle;
50 import android.provider.Settings;
51 import android.text.TextUtils;
52 import android.util.Log;
53 import android.util.SparseArray;
54 
55 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
56 import com.android.launcher3.LauncherSettings.Favorites;
57 import com.android.launcher3.compat.UserHandleCompat;
58 import com.android.launcher3.compat.UserManagerCompat;
59 import com.android.launcher3.config.ProviderConfig;
60 
61 import org.xmlpull.v1.XmlPullParser;
62 import org.xmlpull.v1.XmlPullParserException;
63 
64 import java.io.File;
65 import java.io.IOException;
66 import java.net.URISyntaxException;
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.HashSet;
70 import java.util.List;
71 
72 public class LauncherProvider extends ContentProvider {
73     private static final String TAG = "Launcher.LauncherProvider";
74     private static final boolean LOGD = false;
75 
76     private static final String DATABASE_NAME = "launcher.db";
77 
78     private static final int DATABASE_VERSION = 20;
79 
80     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
81     static final String AUTHORITY = ProviderConfig.AUTHORITY;
82 
83     // Should we attempt to load anything from the com.android.launcher2 provider?
84     static final boolean IMPORT_LAUNCHER2_DATABASE = false;
85 
86     static final String TABLE_FAVORITES = "favorites";
87     static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
88     static final String PARAMETER_NOTIFY = "notify";
89     static final String UPGRADED_FROM_OLD_DATABASE =
90             "UPGRADED_FROM_OLD_DATABASE";
91     static final String EMPTY_DATABASE_CREATED =
92             "EMPTY_DATABASE_CREATED";
93 
94     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
95             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
96 
97     private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
98 
99     private LauncherProviderChangeListener mListener;
100 
101     /**
102      * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
103      * {@link AppWidgetHost#deleteHost()} is called during database creation.
104      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
105      */
106     static final Uri CONTENT_APPWIDGET_RESET_URI =
107             Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
108 
109     private DatabaseHelper mOpenHelper;
110     private static boolean sJustLoadedFromOldDb;
111 
112     @Override
onCreate()113     public boolean onCreate() {
114         final Context context = getContext();
115         mOpenHelper = new DatabaseHelper(context);
116         LauncherAppState.setLauncherProvider(this);
117         return true;
118     }
119 
wasNewDbCreated()120     public boolean wasNewDbCreated() {
121         return mOpenHelper.wasNewDbCreated();
122     }
123 
setLauncherProviderChangeListener(LauncherProviderChangeListener listener)124     public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
125         mListener = listener;
126     }
127 
128     @Override
getType(Uri uri)129     public String getType(Uri uri) {
130         SqlArguments args = new SqlArguments(uri, null, null);
131         if (TextUtils.isEmpty(args.where)) {
132             return "vnd.android.cursor.dir/" + args.table;
133         } else {
134             return "vnd.android.cursor.item/" + args.table;
135         }
136     }
137 
138     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)139     public Cursor query(Uri uri, String[] projection, String selection,
140             String[] selectionArgs, String sortOrder) {
141 
142         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
143         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
144         qb.setTables(args.table);
145 
146         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
147         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
148         result.setNotificationUri(getContext().getContentResolver(), uri);
149 
150         return result;
151     }
152 
dbInsertAndCheck(DatabaseHelper helper, SQLiteDatabase db, String table, String nullColumnHack, ContentValues values)153     private static long dbInsertAndCheck(DatabaseHelper helper,
154             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
155         if (values == null) {
156             throw new RuntimeException("Error: attempting to insert null values");
157         }
158         if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
159             throw new RuntimeException("Error: attempting to add item without specifying an id");
160         }
161         helper.checkId(table, values);
162         return db.insert(table, nullColumnHack, values);
163     }
164 
deleteId(SQLiteDatabase db, long id)165     private static void deleteId(SQLiteDatabase db, long id) {
166         Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
167         SqlArguments args = new SqlArguments(uri, null, null);
168         db.delete(args.table, args.where, args.args);
169     }
170 
171     @Override
insert(Uri uri, ContentValues initialValues)172     public Uri insert(Uri uri, ContentValues initialValues) {
173         SqlArguments args = new SqlArguments(uri);
174 
175         // In very limited cases, we support system|signature permission apps to add to the db
176         String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
177         if (externalAdd != null && "true".equals(externalAdd)) {
178             if (!mOpenHelper.initializeExternalAdd(initialValues)) {
179                 return null;
180             }
181         }
182 
183         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
184         addModifiedTime(initialValues);
185         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
186         if (rowId <= 0) return null;
187 
188         uri = ContentUris.withAppendedId(uri, rowId);
189         sendNotify(uri);
190 
191         return uri;
192     }
193 
194 
195     @Override
bulkInsert(Uri uri, ContentValues[] values)196     public int bulkInsert(Uri uri, ContentValues[] values) {
197         SqlArguments args = new SqlArguments(uri);
198 
199         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
200         db.beginTransaction();
201         try {
202             int numValues = values.length;
203             for (int i = 0; i < numValues; i++) {
204                 addModifiedTime(values[i]);
205                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
206                     return 0;
207                 }
208             }
209             db.setTransactionSuccessful();
210         } finally {
211             db.endTransaction();
212         }
213 
214         sendNotify(uri);
215         return values.length;
216     }
217 
218     @Override
applyBatch(ArrayList<ContentProviderOperation> operations)219     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
220             throws OperationApplicationException {
221         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
222         db.beginTransaction();
223         try {
224             ContentProviderResult[] result =  super.applyBatch(operations);
225             db.setTransactionSuccessful();
226             return result;
227         } finally {
228             db.endTransaction();
229         }
230     }
231 
232     @Override
delete(Uri uri, String selection, String[] selectionArgs)233     public int delete(Uri uri, String selection, String[] selectionArgs) {
234         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
235 
236         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
237         int count = db.delete(args.table, args.where, args.args);
238         if (count > 0) sendNotify(uri);
239 
240         return count;
241     }
242 
243     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)244     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
245         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
246 
247         addModifiedTime(values);
248         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
249         int count = db.update(args.table, values, args.where, args.args);
250         if (count > 0) sendNotify(uri);
251 
252         return count;
253     }
254 
sendNotify(Uri uri)255     private void sendNotify(Uri uri) {
256         String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
257         if (notify == null || "true".equals(notify)) {
258             getContext().getContentResolver().notifyChange(uri, null);
259         }
260 
261         // always notify the backup agent
262         LauncherBackupAgentHelper.dataChanged(getContext());
263         if (mListener != null) {
264             mListener.onLauncherProviderChange();
265         }
266     }
267 
addModifiedTime(ContentValues values)268     private void addModifiedTime(ContentValues values) {
269         values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
270     }
271 
generateNewItemId()272     public long generateNewItemId() {
273         return mOpenHelper.generateNewItemId();
274     }
275 
updateMaxItemId(long id)276     public void updateMaxItemId(long id) {
277         mOpenHelper.updateMaxItemId(id);
278     }
279 
generateNewScreenId()280     public long generateNewScreenId() {
281         return mOpenHelper.generateNewScreenId();
282     }
283 
284     // This is only required one time while loading the workspace during the
285     // upgrade path, and should never be called from anywhere else.
updateMaxScreenId(long maxScreenId)286     public void updateMaxScreenId(long maxScreenId) {
287         mOpenHelper.updateMaxScreenId(maxScreenId);
288     }
289 
290     /**
291      * @param Should we load the old db for upgrade? first run only.
292      */
justLoadedOldDb()293     synchronized public boolean justLoadedOldDb() {
294         String spKey = LauncherAppState.getSharedPreferencesKey();
295         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
296 
297         boolean loadedOldDb = false || sJustLoadedFromOldDb;
298 
299         sJustLoadedFromOldDb = false;
300         if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
301 
302             SharedPreferences.Editor editor = sp.edit();
303             editor.remove(UPGRADED_FROM_OLD_DATABASE);
304             editor.commit();
305             loadedOldDb = true;
306         }
307         return loadedOldDb;
308     }
309 
310     /**
311      * Clears all the data for a fresh start.
312      */
createEmptyDB()313     synchronized public void createEmptyDB() {
314         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
315     }
316 
317     /**
318      * Loads the default workspace based on the following priority scheme:
319      *   1) From a package provided by play store
320      *   2) From a partner configuration APK, already in the system image
321      *   3) The default configuration for the particular device
322      */
loadDefaultFavoritesIfNecessary()323     synchronized public void loadDefaultFavoritesIfNecessary() {
324         String spKey = LauncherAppState.getSharedPreferencesKey();
325         SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
326 
327         if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
328             Log.d(TAG, "loading default workspace");
329 
330             WorkspaceLoader loader = AutoInstallsLayout.get(getContext(),
331                     mOpenHelper.mAppWidgetHost, mOpenHelper);
332 
333             if (loader == null) {
334                 final Partner partner = Partner.get(getContext().getPackageManager());
335                 if (partner != null && partner.hasDefaultLayout()) {
336                     final Resources partnerRes = partner.getResources();
337                     int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
338                             "xml", partner.getPackageName());
339                     if (workspaceResId != 0) {
340                         loader = new SimpleWorkspaceLoader(mOpenHelper, partnerRes, workspaceResId);
341                     }
342                 }
343             }
344 
345             if (loader == null) {
346                 loader = new SimpleWorkspaceLoader(mOpenHelper, getContext().getResources(),
347                         getDefaultWorkspaceResourceId());
348             }
349 
350             // Populate favorites table with initial favorites
351             SharedPreferences.Editor editor = sp.edit().remove(EMPTY_DATABASE_CREATED);
352             mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader);
353             editor.commit();
354         }
355     }
356 
migrateLauncher2Shortcuts()357     public void migrateLauncher2Shortcuts() {
358         mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(),
359                 Uri.parse(getContext().getString(R.string.old_launcher_provider_uri)));
360     }
361 
getDefaultWorkspaceResourceId()362     private static int getDefaultWorkspaceResourceId() {
363         LauncherAppState app = LauncherAppState.getInstance();
364         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
365         if (LauncherAppState.isDisableAllApps()) {
366             return grid.defaultNoAllAppsLayoutId;
367         } else {
368             return grid.defaultLayoutId;
369         }
370     }
371 
372     private static interface ContentValuesCallback {
onRow(ContentValues values)373         public void onRow(ContentValues values);
374     }
375 
shouldImportLauncher2Database(Context context)376     private static boolean shouldImportLauncher2Database(Context context) {
377         boolean isTablet = context.getResources().getBoolean(R.bool.is_tablet);
378 
379         // We don't import the old databse for tablets, as the grid size has changed.
380         return !isTablet && IMPORT_LAUNCHER2_DATABASE;
381     }
382 
deleteDatabase()383     public void deleteDatabase() {
384         // Are you sure? (y/n)
385         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
386         final File dbFile = new File(db.getPath());
387         mOpenHelper.close();
388         if (dbFile.exists()) {
389             SQLiteDatabase.deleteDatabase(dbFile);
390         }
391         mOpenHelper = new DatabaseHelper(getContext());
392     }
393 
394     private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
395         private static final String TAG_RESOLVE = "resolve";
396         private static final String TAG_FAVORITES = "favorites";
397         private static final String TAG_FAVORITE = "favorite";
398         private static final String TAG_APPWIDGET = "appwidget";
399         private static final String TAG_SHORTCUT = "shortcut";
400         private static final String TAG_FOLDER = "folder";
401         private static final String TAG_PARTNER_FOLDER = "partner-folder";
402         private static final String TAG_EXTRA = "extra";
403         private static final String TAG_INCLUDE = "include";
404 
405         // Style attrs -- "Favorite"
406         private static final String ATTR_CLASS_NAME = "className";
407         private static final String ATTR_PACKAGE_NAME = "packageName";
408         private static final String ATTR_CONTAINER = "container";
409         private static final String ATTR_SCREEN = "screen";
410         private static final String ATTR_X = "x";
411         private static final String ATTR_Y = "y";
412         private static final String ATTR_SPAN_X = "spanX";
413         private static final String ATTR_SPAN_Y = "spanY";
414         private static final String ATTR_ICON = "icon";
415         private static final String ATTR_TITLE = "title";
416         private static final String ATTR_URI = "uri";
417 
418         // Style attrs -- "Include"
419         private static final String ATTR_WORKSPACE = "workspace";
420 
421         // Style attrs -- "Extra"
422         private static final String ATTR_KEY = "key";
423         private static final String ATTR_VALUE = "value";
424 
425         private final Context mContext;
426         private final PackageManager mPackageManager;
427         private final AppWidgetHost mAppWidgetHost;
428         private long mMaxItemId = -1;
429         private long mMaxScreenId = -1;
430 
431         private boolean mNewDbCreated = false;
432 
DatabaseHelper(Context context)433         DatabaseHelper(Context context) {
434             super(context, DATABASE_NAME, null, DATABASE_VERSION);
435             mContext = context;
436             mPackageManager = context.getPackageManager();
437             mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
438 
439             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
440             // the DB here
441             if (mMaxItemId == -1) {
442                 mMaxItemId = initializeMaxItemId(getWritableDatabase());
443             }
444             if (mMaxScreenId == -1) {
445                 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
446             }
447         }
448 
wasNewDbCreated()449         public boolean wasNewDbCreated() {
450             return mNewDbCreated;
451         }
452 
453         /**
454          * Send notification that we've deleted the {@link AppWidgetHost},
455          * probably as part of the initial database creation. The receiver may
456          * want to re-call {@link AppWidgetHost#startListening()} to ensure
457          * callbacks are correctly set.
458          */
sendAppWidgetResetNotify()459         private void sendAppWidgetResetNotify() {
460             final ContentResolver resolver = mContext.getContentResolver();
461             resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
462         }
463 
464         @Override
onCreate(SQLiteDatabase db)465         public void onCreate(SQLiteDatabase db) {
466             if (LOGD) Log.d(TAG, "creating new launcher database");
467 
468             mMaxItemId = 1;
469             mMaxScreenId = 0;
470             mNewDbCreated = true;
471 
472             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
473             long userSerialNumber = userManager.getSerialNumberForUser(
474                     UserHandleCompat.myUserHandle());
475 
476             db.execSQL("CREATE TABLE favorites (" +
477                     "_id INTEGER PRIMARY KEY," +
478                     "title TEXT," +
479                     "intent TEXT," +
480                     "container INTEGER," +
481                     "screen INTEGER," +
482                     "cellX INTEGER," +
483                     "cellY INTEGER," +
484                     "spanX INTEGER," +
485                     "spanY INTEGER," +
486                     "itemType INTEGER," +
487                     "appWidgetId INTEGER NOT NULL DEFAULT -1," +
488                     "isShortcut INTEGER," +
489                     "iconType INTEGER," +
490                     "iconPackage TEXT," +
491                     "iconResource TEXT," +
492                     "icon BLOB," +
493                     "uri TEXT," +
494                     "displayMode INTEGER," +
495                     "appWidgetProvider TEXT," +
496                     "modified INTEGER NOT NULL DEFAULT 0," +
497                     "restored INTEGER NOT NULL DEFAULT 0," +
498                     "profileId INTEGER DEFAULT " + userSerialNumber +
499                     ");");
500             addWorkspacesTable(db);
501 
502             // Database was just created, so wipe any previous widgets
503             if (mAppWidgetHost != null) {
504                 mAppWidgetHost.deleteHost();
505                 sendAppWidgetResetNotify();
506             }
507 
508             if (shouldImportLauncher2Database(mContext)) {
509                 // Try converting the old database
510                 ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
511                     public void onRow(ContentValues values) {
512                         int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
513                         if (container == Favorites.CONTAINER_DESKTOP) {
514                             int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
515                             screen = (int) upgradeLauncherDb_permuteScreens(screen);
516                             values.put(LauncherSettings.Favorites.SCREEN, screen);
517                         }
518                     }
519                 };
520                 Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
521                         "/old_favorites?notify=true");
522                 if (!convertDatabase(db, uri, permuteScreensCb, true)) {
523                     // Try and upgrade from the Launcher2 db
524                     uri = Uri.parse(mContext.getString(R.string.old_launcher_provider_uri));
525                     if (!convertDatabase(db, uri, permuteScreensCb, false)) {
526                         // If we fail, then set a flag to load the default workspace
527                         setFlagEmptyDbCreated();
528                         return;
529                     }
530                 }
531                 // Right now, in non-default workspace cases, we want to run the final
532                 // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
533                 // set that flag too.
534                 setFlagJustLoadedOldDb();
535             } else {
536                 // Fresh and clean launcher DB.
537                 mMaxItemId = initializeMaxItemId(db);
538                 setFlagEmptyDbCreated();
539             }
540         }
541 
addWorkspacesTable(SQLiteDatabase db)542         private void addWorkspacesTable(SQLiteDatabase db) {
543             db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
544                     LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
545                     LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
546                     LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
547                     ");");
548         }
549 
removeOrphanedItems(SQLiteDatabase db)550         private void removeOrphanedItems(SQLiteDatabase db) {
551             // Delete items directly on the workspace who's screen id doesn't exist
552             //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
553             //   AND container = -100"
554             String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
555                     " WHERE " +
556                     LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
557                     LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
558                     " AND " +
559                     LauncherSettings.Favorites.CONTAINER + " = " +
560                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
561             db.execSQL(removeOrphanedDesktopItems);
562 
563             // Delete items contained in folders which no longer exist (after above statement)
564             //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
565             //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
566             String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
567                     " WHERE " +
568                     LauncherSettings.Favorites.CONTAINER + " <> " +
569                     LauncherSettings.Favorites.CONTAINER_DESKTOP +
570                     " AND "
571                     + LauncherSettings.Favorites.CONTAINER + " <> " +
572                     LauncherSettings.Favorites.CONTAINER_HOTSEAT +
573                     " AND "
574                     + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
575                     LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
576                     " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
577                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
578             db.execSQL(removeOrphanedFolderItems);
579         }
580 
setFlagJustLoadedOldDb()581         private void setFlagJustLoadedOldDb() {
582             String spKey = LauncherAppState.getSharedPreferencesKey();
583             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
584             SharedPreferences.Editor editor = sp.edit();
585             editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
586             editor.putBoolean(EMPTY_DATABASE_CREATED, false);
587             editor.commit();
588         }
589 
setFlagEmptyDbCreated()590         private void setFlagEmptyDbCreated() {
591             String spKey = LauncherAppState.getSharedPreferencesKey();
592             SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
593             SharedPreferences.Editor editor = sp.edit();
594             editor.putBoolean(EMPTY_DATABASE_CREATED, true);
595             editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
596             editor.commit();
597         }
598 
599         // We rearrange the screens from the old launcher
600         // 12345 -> 34512
upgradeLauncherDb_permuteScreens(long screen)601         private long upgradeLauncherDb_permuteScreens(long screen) {
602             if (screen >= 2) {
603                 return screen - 2;
604             } else {
605                 return screen + 3;
606             }
607         }
608 
convertDatabase(SQLiteDatabase db, Uri uri, ContentValuesCallback cb, boolean deleteRows)609         private boolean convertDatabase(SQLiteDatabase db, Uri uri,
610                                         ContentValuesCallback cb, boolean deleteRows) {
611             if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
612             boolean converted = false;
613 
614             final ContentResolver resolver = mContext.getContentResolver();
615             Cursor cursor = null;
616 
617             try {
618                 cursor = resolver.query(uri, null, null, null, null);
619             } catch (Exception e) {
620                 // Ignore
621             }
622 
623             // We already have a favorites database in the old provider
624             if (cursor != null) {
625                 try {
626                      if (cursor.getCount() > 0) {
627                         converted = copyFromCursor(db, cursor, cb) > 0;
628                         if (converted && deleteRows) {
629                             resolver.delete(uri, null, null);
630                         }
631                     }
632                 } finally {
633                     cursor.close();
634                 }
635             }
636 
637             if (converted) {
638                 // Convert widgets from this import into widgets
639                 if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
640                 convertWidgets(db);
641 
642                 // Update max item id
643                 mMaxItemId = initializeMaxItemId(db);
644                 if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
645             }
646 
647             return converted;
648         }
649 
copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb)650         private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
651             final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
652             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
653             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
654             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
655             final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
656             final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
657             final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
658             final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
659             final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
660             final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
661             final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
662             final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
663             final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
664             final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
665 
666             ContentValues[] rows = new ContentValues[c.getCount()];
667             int i = 0;
668             while (c.moveToNext()) {
669                 ContentValues values = new ContentValues(c.getColumnCount());
670                 values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
671                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
672                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
673                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
674                 values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
675                 values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
676                 values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
677                 values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
678                 values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
679                 values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
680                 values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
681                 values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
682                 values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
683                 values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
684                 values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
685                 if (cb != null) {
686                     cb.onRow(values);
687                 }
688                 rows[i++] = values;
689             }
690 
691             int total = 0;
692             if (i > 0) {
693                 db.beginTransaction();
694                 try {
695                     int numValues = rows.length;
696                     for (i = 0; i < numValues; i++) {
697                         if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
698                             return 0;
699                         } else {
700                             total++;
701                         }
702                     }
703                     db.setTransactionSuccessful();
704                 } finally {
705                     db.endTransaction();
706                 }
707             }
708 
709             return total;
710         }
711 
712         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)713         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
714             if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
715 
716             int version = oldVersion;
717             if (version < 3) {
718                 // upgrade 1,2 -> 3 added appWidgetId column
719                 db.beginTransaction();
720                 try {
721                     // Insert new column for holding appWidgetIds
722                     db.execSQL("ALTER TABLE favorites " +
723                         "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
724                     db.setTransactionSuccessful();
725                     version = 3;
726                 } catch (SQLException ex) {
727                     // Old version remains, which means we wipe old data
728                     Log.e(TAG, ex.getMessage(), ex);
729                 } finally {
730                     db.endTransaction();
731                 }
732 
733                 // Convert existing widgets only if table upgrade was successful
734                 if (version == 3) {
735                     convertWidgets(db);
736                 }
737             }
738 
739             if (version < 4) {
740                 version = 4;
741             }
742 
743             // Where's version 5?
744             // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
745             // - Passion shipped on 2.1 with version 6 of launcher3
746             // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
747             //   but version 5 on there was the updateContactsShortcuts change
748             //   which was version 6 in launcher 2 (first shipped on passion 2.1r1).
749             // The updateContactsShortcuts change is idempotent, so running it twice
750             // is okay so we'll do that when upgrading the devices that shipped with it.
751             if (version < 6) {
752                 // We went from 3 to 5 screens. Move everything 1 to the right
753                 db.beginTransaction();
754                 try {
755                     db.execSQL("UPDATE favorites SET screen=(screen + 1);");
756                     db.setTransactionSuccessful();
757                 } catch (SQLException ex) {
758                     // Old version remains, which means we wipe old data
759                     Log.e(TAG, ex.getMessage(), ex);
760                 } finally {
761                     db.endTransaction();
762                 }
763 
764                // We added the fast track.
765                 if (updateContactsShortcuts(db)) {
766                     version = 6;
767                 }
768             }
769 
770             if (version < 7) {
771                 // Version 7 gets rid of the special search widget.
772                 convertWidgets(db);
773                 version = 7;
774             }
775 
776             if (version < 8) {
777                 // Version 8 (froyo) has the icons all normalized.  This should
778                 // already be the case in practice, but we now rely on it and don't
779                 // resample the images each time.
780                 normalizeIcons(db);
781                 version = 8;
782             }
783 
784             if (version < 9) {
785                 // The max id is not yet set at this point (onUpgrade is triggered in the ctor
786                 // before it gets a change to get set, so we need to read it here when we use it)
787                 if (mMaxItemId == -1) {
788                     mMaxItemId = initializeMaxItemId(db);
789                 }
790 
791                 // Add default hotseat icons
792                 loadFavorites(db, new SimpleWorkspaceLoader(this, mContext.getResources(),
793                         R.xml.update_workspace));
794                 version = 9;
795             }
796 
797             // We bumped the version three time during JB, once to update the launch flags, once to
798             // update the override for the default launch animation and once to set the mimetype
799             // to improve startup performance
800             if (version < 12) {
801                 // Contact shortcuts need a different set of flags to be launched now
802                 // The updateContactsShortcuts change is idempotent, so we can keep using it like
803                 // back in the Donut days
804                 updateContactsShortcuts(db);
805                 version = 12;
806             }
807 
808             if (version < 13) {
809                 // With the new shrink-wrapped and re-orderable workspaces, it makes sense
810                 // to persist workspace screens and their relative order.
811                 mMaxScreenId = 0;
812 
813                 // This will never happen in the wild, but when we switch to using workspace
814                 // screen ids, redo the import from old launcher.
815                 sJustLoadedFromOldDb = true;
816 
817                 addWorkspacesTable(db);
818                 version = 13;
819             }
820 
821             if (version < 14) {
822                 db.beginTransaction();
823                 try {
824                     // Insert new column for holding widget provider name
825                     db.execSQL("ALTER TABLE favorites " +
826                             "ADD COLUMN appWidgetProvider TEXT;");
827                     db.setTransactionSuccessful();
828                     version = 14;
829                 } catch (SQLException ex) {
830                     // Old version remains, which means we wipe old data
831                     Log.e(TAG, ex.getMessage(), ex);
832                 } finally {
833                     db.endTransaction();
834                 }
835             }
836 
837             if (version < 15) {
838                 db.beginTransaction();
839                 try {
840                     // Insert new column for holding update timestamp
841                     db.execSQL("ALTER TABLE favorites " +
842                             "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
843                     db.execSQL("ALTER TABLE workspaceScreens " +
844                             "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
845                     db.setTransactionSuccessful();
846                     version = 15;
847                 } catch (SQLException ex) {
848                     // Old version remains, which means we wipe old data
849                     Log.e(TAG, ex.getMessage(), ex);
850                 } finally {
851                     db.endTransaction();
852                 }
853             }
854 
855 
856             if (version < 16) {
857                 db.beginTransaction();
858                 try {
859                     // Insert new column for holding restore status
860                     db.execSQL("ALTER TABLE favorites " +
861                             "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
862                     db.setTransactionSuccessful();
863                     version = 16;
864                 } catch (SQLException ex) {
865                     // Old version remains, which means we wipe old data
866                     Log.e(TAG, ex.getMessage(), ex);
867                 } finally {
868                     db.endTransaction();
869                 }
870             }
871 
872             if (version < 17) {
873                 // We use the db version upgrade here to identify users who may not have seen
874                 // clings yet (because they weren't available), but for whom the clings are now
875                 // available (tablet users). Because one of the possible cling flows (migration)
876                 // is very destructive (wipes out workspaces), we want to prevent this from showing
877                 // until clear data. We do so by marking that the clings have been shown.
878                 LauncherClings.synchonouslyMarkFirstRunClingDismissed(mContext);
879                 version = 17;
880             }
881 
882             if (version < 18) {
883                 // No-op
884                 version = 18;
885             }
886 
887             if (version < 19) {
888                 // Due to a data loss bug, some users may have items associated with screen ids
889                 // which no longer exist. Since this can cause other problems, and since the user
890                 // will never see these items anyway, we use database upgrade as an opportunity to
891                 // clean things up.
892                 removeOrphanedItems(db);
893                 version = 19;
894             }
895 
896             if (version < 20) {
897                 // Add userId column
898                 if (addProfileColumn(db)) {
899                     version = 20;
900                 }
901                 // else old version remains, which means we wipe old data
902             }
903 
904             if (version != DATABASE_VERSION) {
905                 Log.w(TAG, "Destroying all old data.");
906                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
907                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
908 
909                 onCreate(db);
910             }
911         }
912 
913         @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)914         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
915             // This shouldn't happen -- throw our hands up in the air and start over.
916             Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
917                     ". Wiping databse.");
918             createEmptyDB(db);
919         }
920 
921 
922         /**
923          * Clears all the data for a fresh start.
924          */
createEmptyDB(SQLiteDatabase db)925         public void createEmptyDB(SQLiteDatabase db) {
926             db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
927             db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
928             onCreate(db);
929         }
930 
addProfileColumn(SQLiteDatabase db)931         private boolean addProfileColumn(SQLiteDatabase db) {
932             db.beginTransaction();
933             try {
934                 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
935                 // Default to the serial number of this user, for older
936                 // shortcuts.
937                 long userSerialNumber = userManager.getSerialNumberForUser(
938                         UserHandleCompat.myUserHandle());
939                 // Insert new column for holding user serial number
940                 db.execSQL("ALTER TABLE favorites " +
941                         "ADD COLUMN profileId INTEGER DEFAULT "
942                                         + userSerialNumber + ";");
943                 db.setTransactionSuccessful();
944             } catch (SQLException ex) {
945                 // Old version remains, which means we wipe old data
946                 Log.e(TAG, ex.getMessage(), ex);
947                 return false;
948             } finally {
949                 db.endTransaction();
950             }
951             return true;
952         }
953 
updateContactsShortcuts(SQLiteDatabase db)954         private boolean updateContactsShortcuts(SQLiteDatabase db) {
955             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
956                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
957 
958             Cursor c = null;
959             final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
960             db.beginTransaction();
961             try {
962                 // Select and iterate through each matching widget
963                 c = db.query(TABLE_FAVORITES,
964                         new String[] { Favorites._ID, Favorites.INTENT },
965                         selectWhere, null, null, null, null);
966                 if (c == null) return false;
967 
968                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
969 
970                 final int idIndex = c.getColumnIndex(Favorites._ID);
971                 final int intentIndex = c.getColumnIndex(Favorites.INTENT);
972 
973                 while (c.moveToNext()) {
974                     long favoriteId = c.getLong(idIndex);
975                     final String intentUri = c.getString(intentIndex);
976                     if (intentUri != null) {
977                         try {
978                             final Intent intent = Intent.parseUri(intentUri, 0);
979                             android.util.Log.d("Home", intent.toString());
980                             final Uri uri = intent.getData();
981                             if (uri != null) {
982                                 final String data = uri.toString();
983                                 if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
984                                         actionQuickContact.equals(intent.getAction())) &&
985                                         (data.startsWith("content://contacts/people/") ||
986                                         data.startsWith("content://com.android.contacts/" +
987                                                 "contacts/lookup/"))) {
988 
989                                     final Intent newIntent = new Intent(actionQuickContact);
990                                     // When starting from the launcher, start in a new, cleared task
991                                     // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
992                                     // clear the whole thing preemptively here since
993                                     // QuickContactActivity will finish itself when launching other
994                                     // detail activities.
995                                     newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
996                                             Intent.FLAG_ACTIVITY_CLEAR_TASK);
997                                     newIntent.putExtra(
998                                             Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
999                                     newIntent.setData(uri);
1000                                     // Determine the type and also put that in the shortcut
1001                                     // (that can speed up launch a bit)
1002                                     newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
1003 
1004                                     final ContentValues values = new ContentValues();
1005                                     values.put(LauncherSettings.Favorites.INTENT,
1006                                             newIntent.toUri(0));
1007 
1008                                     String updateWhere = Favorites._ID + "=" + favoriteId;
1009                                     db.update(TABLE_FAVORITES, values, updateWhere, null);
1010                                 }
1011                             }
1012                         } catch (RuntimeException ex) {
1013                             Log.e(TAG, "Problem upgrading shortcut", ex);
1014                         } catch (URISyntaxException e) {
1015                             Log.e(TAG, "Problem upgrading shortcut", e);
1016                         }
1017                     }
1018                 }
1019 
1020                 db.setTransactionSuccessful();
1021             } catch (SQLException ex) {
1022                 Log.w(TAG, "Problem while upgrading contacts", ex);
1023                 return false;
1024             } finally {
1025                 db.endTransaction();
1026                 if (c != null) {
1027                     c.close();
1028                 }
1029             }
1030 
1031             return true;
1032         }
1033 
normalizeIcons(SQLiteDatabase db)1034         private void normalizeIcons(SQLiteDatabase db) {
1035             Log.d(TAG, "normalizing icons");
1036 
1037             db.beginTransaction();
1038             Cursor c = null;
1039             SQLiteStatement update = null;
1040             try {
1041                 boolean logged = false;
1042                 update = db.compileStatement("UPDATE favorites "
1043                         + "SET icon=? WHERE _id=?");
1044 
1045                 c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
1046                         Favorites.ICON_TYPE_BITMAP, null);
1047 
1048                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
1049                 final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
1050 
1051                 while (c.moveToNext()) {
1052                     long id = c.getLong(idIndex);
1053                     byte[] data = c.getBlob(iconIndex);
1054                     try {
1055                         Bitmap bitmap = Utilities.resampleIconBitmap(
1056                                 BitmapFactory.decodeByteArray(data, 0, data.length),
1057                                 mContext);
1058                         if (bitmap != null) {
1059                             update.bindLong(1, id);
1060                             data = ItemInfo.flattenBitmap(bitmap);
1061                             if (data != null) {
1062                                 update.bindBlob(2, data);
1063                                 update.execute();
1064                             }
1065                             bitmap.recycle();
1066                         }
1067                     } catch (Exception e) {
1068                         if (!logged) {
1069                             Log.e(TAG, "Failed normalizing icon " + id, e);
1070                         } else {
1071                             Log.e(TAG, "Also failed normalizing icon " + id);
1072                         }
1073                         logged = true;
1074                     }
1075                 }
1076                 db.setTransactionSuccessful();
1077             } catch (SQLException ex) {
1078                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
1079             } finally {
1080                 db.endTransaction();
1081                 if (update != null) {
1082                     update.close();
1083                 }
1084                 if (c != null) {
1085                     c.close();
1086                 }
1087             }
1088         }
1089 
1090         // Generates a new ID to use for an object in your database. This method should be only
1091         // called from the main UI thread. As an exception, we do call it when we call the
1092         // constructor from the worker thread; however, this doesn't extend until after the
1093         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1094         // after that point
1095         @Override
generateNewItemId()1096         public long generateNewItemId() {
1097             if (mMaxItemId < 0) {
1098                 throw new RuntimeException("Error: max item id was not initialized");
1099             }
1100             mMaxItemId += 1;
1101             return mMaxItemId;
1102         }
1103 
1104         @Override
insertAndCheck(SQLiteDatabase db, ContentValues values)1105         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
1106             return dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
1107         }
1108 
updateMaxItemId(long id)1109         public void updateMaxItemId(long id) {
1110             mMaxItemId = id + 1;
1111         }
1112 
checkId(String table, ContentValues values)1113         public void checkId(String table, ContentValues values) {
1114             long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
1115             if (table == LauncherProvider.TABLE_WORKSPACE_SCREENS) {
1116                 mMaxScreenId = Math.max(id, mMaxScreenId);
1117             }  else {
1118                 mMaxItemId = Math.max(id, mMaxItemId);
1119             }
1120         }
1121 
initializeMaxItemId(SQLiteDatabase db)1122         private long initializeMaxItemId(SQLiteDatabase db) {
1123             Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
1124 
1125             // get the result
1126             final int maxIdIndex = 0;
1127             long id = -1;
1128             if (c != null && c.moveToNext()) {
1129                 id = c.getLong(maxIdIndex);
1130             }
1131             if (c != null) {
1132                 c.close();
1133             }
1134 
1135             if (id == -1) {
1136                 throw new RuntimeException("Error: could not query max item id");
1137             }
1138 
1139             return id;
1140         }
1141 
1142         // Generates a new ID to use for an workspace screen in your database. This method
1143         // should be only called from the main UI thread. As an exception, we do call it when we
1144         // call the constructor from the worker thread; however, this doesn't extend until after the
1145         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1146         // after that point
generateNewScreenId()1147         public long generateNewScreenId() {
1148             if (mMaxScreenId < 0) {
1149                 throw new RuntimeException("Error: max screen id was not initialized");
1150             }
1151             mMaxScreenId += 1;
1152             // Log to disk
1153             Launcher.addDumpLog(TAG, "11683562 - generateNewScreenId(): " + mMaxScreenId, true);
1154             return mMaxScreenId;
1155         }
1156 
updateMaxScreenId(long maxScreenId)1157         public void updateMaxScreenId(long maxScreenId) {
1158             // Log to disk
1159             Launcher.addDumpLog(TAG, "11683562 - updateMaxScreenId(): " + maxScreenId, true);
1160             mMaxScreenId = maxScreenId;
1161         }
1162 
initializeMaxScreenId(SQLiteDatabase db)1163         private long initializeMaxScreenId(SQLiteDatabase db) {
1164             Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1165 
1166             // get the result
1167             final int maxIdIndex = 0;
1168             long id = -1;
1169             if (c != null && c.moveToNext()) {
1170                 id = c.getLong(maxIdIndex);
1171             }
1172             if (c != null) {
1173                 c.close();
1174             }
1175 
1176             if (id == -1) {
1177                 throw new RuntimeException("Error: could not query max screen id");
1178             }
1179 
1180             // Log to disk
1181             Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
1182             return id;
1183         }
1184 
1185         /**
1186          * Upgrade existing clock and photo frame widgets into their new widget
1187          * equivalents.
1188          */
convertWidgets(SQLiteDatabase db)1189         private void convertWidgets(SQLiteDatabase db) {
1190             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1191             final int[] bindSources = new int[] {
1192                     Favorites.ITEM_TYPE_WIDGET_CLOCK,
1193                     Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
1194                     Favorites.ITEM_TYPE_WIDGET_SEARCH,
1195             };
1196 
1197             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
1198 
1199             Cursor c = null;
1200 
1201             db.beginTransaction();
1202             try {
1203                 // Select and iterate through each matching widget
1204                 c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
1205                         selectWhere, null, null, null, null);
1206 
1207                 if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
1208 
1209                 final ContentValues values = new ContentValues();
1210                 while (c != null && c.moveToNext()) {
1211                     long favoriteId = c.getLong(0);
1212                     int favoriteType = c.getInt(1);
1213 
1214                     // Allocate and update database with new appWidgetId
1215                     try {
1216                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
1217 
1218                         if (LOGD) {
1219                             Log.d(TAG, "allocated appWidgetId=" + appWidgetId
1220                                     + " for favoriteId=" + favoriteId);
1221                         }
1222                         values.clear();
1223                         values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
1224                         values.put(Favorites.APPWIDGET_ID, appWidgetId);
1225 
1226                         // Original widgets might not have valid spans when upgrading
1227                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
1228                             values.put(LauncherSettings.Favorites.SPANX, 4);
1229                             values.put(LauncherSettings.Favorites.SPANY, 1);
1230                         } else {
1231                             values.put(LauncherSettings.Favorites.SPANX, 2);
1232                             values.put(LauncherSettings.Favorites.SPANY, 2);
1233                         }
1234 
1235                         String updateWhere = Favorites._ID + "=" + favoriteId;
1236                         db.update(TABLE_FAVORITES, values, updateWhere, null);
1237 
1238                         if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
1239                             // TODO: check return value
1240                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1241                                     new ComponentName("com.android.alarmclock",
1242                                     "com.android.alarmclock.AnalogAppWidgetProvider"));
1243                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
1244                             // TODO: check return value
1245                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1246                                     new ComponentName("com.android.camera",
1247                                     "com.android.camera.PhotoAppWidgetProvider"));
1248                         } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
1249                             // TODO: check return value
1250                             appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
1251                                     getSearchWidgetProvider());
1252                         }
1253                     } catch (RuntimeException ex) {
1254                         Log.e(TAG, "Problem allocating appWidgetId", ex);
1255                     }
1256                 }
1257 
1258                 db.setTransactionSuccessful();
1259             } catch (SQLException ex) {
1260                 Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
1261             } finally {
1262                 db.endTransaction();
1263                 if (c != null) {
1264                     c.close();
1265                 }
1266             }
1267 
1268             // Update max item id
1269             mMaxItemId = initializeMaxItemId(db);
1270             if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
1271         }
1272 
initializeExternalAdd(ContentValues values)1273         private boolean initializeExternalAdd(ContentValues values) {
1274             // 1. Ensure that externally added items have a valid item id
1275             long id = generateNewItemId();
1276             values.put(LauncherSettings.Favorites._ID, id);
1277 
1278             // 2. In the case of an app widget, and if no app widget id is specified, we
1279             // attempt allocate and bind the widget.
1280             Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
1281             if (itemType != null &&
1282                     itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
1283                     !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
1284 
1285                 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1286                 ComponentName cn = ComponentName.unflattenFromString(
1287                         values.getAsString(Favorites.APPWIDGET_PROVIDER));
1288 
1289                 if (cn != null) {
1290                     try {
1291                         int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
1292                         values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
1293                         if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
1294                             return false;
1295                         }
1296                     } catch (RuntimeException e) {
1297                         Log.e(TAG, "Failed to initialize external widget", e);
1298                         return false;
1299                     }
1300                 } else {
1301                     return false;
1302                 }
1303             }
1304 
1305             // Add screen id if not present
1306             long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
1307             if (!addScreenIdIfNecessary(screenId)) {
1308                 return false;
1309             }
1310             return true;
1311         }
1312 
1313         // Returns true of screen id exists, or if successfully added
addScreenIdIfNecessary(long screenId)1314         private boolean addScreenIdIfNecessary(long screenId) {
1315             if (!hasScreenId(screenId)) {
1316                 int rank = getMaxScreenRank() + 1;
1317 
1318                 ContentValues v = new ContentValues();
1319                 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1320                 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1321                 if (dbInsertAndCheck(this, getWritableDatabase(),
1322                         TABLE_WORKSPACE_SCREENS, null, v) < 0) {
1323                     return false;
1324                 }
1325             }
1326             return true;
1327         }
1328 
hasScreenId(long screenId)1329         private boolean hasScreenId(long screenId) {
1330             SQLiteDatabase db = getWritableDatabase();
1331             Cursor c = db.rawQuery("SELECT * FROM " + TABLE_WORKSPACE_SCREENS + " WHERE "
1332                     + LauncherSettings.WorkspaceScreens._ID + " = " + screenId, null);
1333             if (c != null) {
1334                 int count = c.getCount();
1335                 c.close();
1336                 return count > 0;
1337             } else {
1338                 return false;
1339             }
1340         }
1341 
getMaxScreenRank()1342         private int getMaxScreenRank() {
1343             SQLiteDatabase db = getWritableDatabase();
1344             Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens.SCREEN_RANK
1345                     + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
1346 
1347             // get the result
1348             final int maxRankIndex = 0;
1349             int rank = -1;
1350             if (c != null && c.moveToNext()) {
1351                 rank = c.getInt(maxRankIndex);
1352             }
1353             if (c != null) {
1354                 c.close();
1355             }
1356 
1357             return rank;
1358         }
1359 
beginDocument(XmlPullParser parser, String firstElementName)1360         private static final void beginDocument(XmlPullParser parser, String firstElementName)
1361                 throws XmlPullParserException, IOException {
1362             int type;
1363             while ((type = parser.next()) != XmlPullParser.START_TAG
1364                     && type != XmlPullParser.END_DOCUMENT) {
1365                 ;
1366             }
1367 
1368             if (type != XmlPullParser.START_TAG) {
1369                 throw new XmlPullParserException("No start tag found");
1370             }
1371 
1372             if (!parser.getName().equals(firstElementName)) {
1373                 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
1374                         ", expected " + firstElementName);
1375             }
1376         }
1377 
buildMainIntent()1378         private static Intent buildMainIntent() {
1379             Intent intent = new Intent(Intent.ACTION_MAIN, null);
1380             intent.addCategory(Intent.CATEGORY_LAUNCHER);
1381             return intent;
1382         }
1383 
loadFavorites(SQLiteDatabase db, WorkspaceLoader loader)1384         private int loadFavorites(SQLiteDatabase db, WorkspaceLoader loader) {
1385             ArrayList<Long> screenIds = new ArrayList<Long>();
1386             // TODO: Use multiple loaders with fall-back and transaction.
1387             int count = loader.loadLayout(db, screenIds);
1388 
1389             // Add the screens specified by the items above
1390             Collections.sort(screenIds);
1391             int rank = 0;
1392             ContentValues values = new ContentValues();
1393             for (Long id : screenIds) {
1394                 values.clear();
1395                 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1396                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1397                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
1398                     throw new RuntimeException("Failed initialize screen table"
1399                             + "from default layout");
1400                 }
1401                 rank++;
1402             }
1403 
1404             // Ensure that the max ids are initialized
1405             mMaxItemId = initializeMaxItemId(db);
1406             mMaxScreenId = initializeMaxScreenId(db);
1407 
1408             return count;
1409         }
1410 
1411         /**
1412          * Loads the default set of favorite packages from an xml file.
1413          *
1414          * @param db The database to write the values into
1415          * @param filterContainerId The specific container id of items to load
1416          * @param the set of screenIds which are used by the favorites
1417          */
loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId, ArrayList<Long> screenIds)1418         private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId,
1419                 ArrayList<Long> screenIds) {
1420 
1421             ContentValues values = new ContentValues();
1422             if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
1423 
1424             int count = 0;
1425             try {
1426                 XmlResourceParser parser = res.getXml(workspaceResourceId);
1427                 beginDocument(parser, TAG_FAVORITES);
1428 
1429                 final int depth = parser.getDepth();
1430 
1431                 int type;
1432                 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1433                         parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1434 
1435                     if (type != XmlPullParser.START_TAG) {
1436                         continue;
1437                     }
1438 
1439                     boolean added = false;
1440                     final String name = parser.getName();
1441 
1442                     if (TAG_INCLUDE.equals(name)) {
1443 
1444                         final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
1445 
1446                         if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
1447                                 "", resId));
1448 
1449                         if (resId != 0 && resId != workspaceResourceId) {
1450                             // recursively load some more favorites, why not?
1451                             count += loadFavoritesRecursive(db, res, resId, screenIds);
1452                             added = false;
1453                         } else {
1454                             Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
1455                         }
1456 
1457                         if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
1458                         continue;
1459                     }
1460 
1461                     // Assuming it's a <favorite> at this point
1462                     long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
1463                     String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
1464                     if (strContainer != null) {
1465                         container = Long.valueOf(strContainer);
1466                     }
1467 
1468                     String screen = getAttributeValue(parser, ATTR_SCREEN);
1469                     String x = getAttributeValue(parser, ATTR_X);
1470                     String y = getAttributeValue(parser, ATTR_Y);
1471 
1472                     values.clear();
1473                     values.put(LauncherSettings.Favorites.CONTAINER, container);
1474                     values.put(LauncherSettings.Favorites.SCREEN, screen);
1475                     values.put(LauncherSettings.Favorites.CELLX, x);
1476                     values.put(LauncherSettings.Favorites.CELLY, y);
1477 
1478                     if (LOGD) {
1479                         final String title = getAttributeValue(parser, ATTR_TITLE);
1480                         final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1481                         final String something = title != null ? title : pkg;
1482                         Log.v(TAG, String.format(
1483                                 ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
1484                                 "", name,
1485                                 (something == null ? "" : (" \"" + something + "\"")),
1486                                 container, screen, x, y));
1487                     }
1488 
1489                     if (TAG_FAVORITE.equals(name)) {
1490                         long id = addAppShortcut(db, values, parser);
1491                         added = id >= 0;
1492                     } else if (TAG_APPWIDGET.equals(name)) {
1493                         added = addAppWidget(parser, type, db, values);
1494                     } else if (TAG_SHORTCUT.equals(name)) {
1495                         long id = addUriShortcut(db, values, res, parser);
1496                         added = id >= 0;
1497                     } else if (TAG_RESOLVE.equals(name)) {
1498                         // This looks through the contained favorites (or meta-favorites) and
1499                         // attempts to add them as shortcuts in the fallback group's location
1500                         // until one is added successfully.
1501                         added = false;
1502                         final int groupDepth = parser.getDepth();
1503                         while ((type = parser.next()) != XmlPullParser.END_TAG ||
1504                                 parser.getDepth() > groupDepth) {
1505                             if (type != XmlPullParser.START_TAG) {
1506                                 continue;
1507                             }
1508                             final String fallback_item_name = parser.getName();
1509                             if (!added) {
1510                                 if (TAG_FAVORITE.equals(fallback_item_name)) {
1511                                     final long id = addAppShortcut(db, values, parser);
1512                                     added = id >= 0;
1513                                 } else {
1514                                     Log.e(TAG, "Fallback groups can contain only favorites, found "
1515                                             + fallback_item_name);
1516                                 }
1517                             }
1518                         }
1519                     } else if (TAG_FOLDER.equals(name)) {
1520                         // Folder contents are nested in this XML file
1521                         added = loadFolder(db, values, res, parser);
1522 
1523                     } else if (TAG_PARTNER_FOLDER.equals(name)) {
1524                         // Folder contents come from an external XML resource
1525                         final Partner partner = Partner.get(mPackageManager);
1526                         if (partner != null) {
1527                             final Resources partnerRes = partner.getResources();
1528                             final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
1529                                     "xml", partner.getPackageName());
1530                             if (resId != 0) {
1531                                 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
1532                                 beginDocument(partnerParser, TAG_FOLDER);
1533                                 added = loadFolder(db, values, partnerRes, partnerParser);
1534                             }
1535                         }
1536                     }
1537                     if (added) {
1538                         long screenId = Long.parseLong(screen);
1539                         // Keep track of the set of screens which need to be added to the db.
1540                         if (!screenIds.contains(screenId) &&
1541                                 container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1542                             screenIds.add(screenId);
1543                         }
1544                         count++;
1545                     }
1546                 }
1547             } catch (XmlPullParserException e) {
1548                 Log.w(TAG, "Got exception parsing favorites.", e);
1549             } catch (IOException e) {
1550                 Log.w(TAG, "Got exception parsing favorites.", e);
1551             } catch (RuntimeException e) {
1552                 Log.w(TAG, "Got exception parsing favorites.", e);
1553             }
1554             return count;
1555         }
1556 
1557         /**
1558          * Parse folder items starting at {@link XmlPullParser} location. Allow recursive
1559          * includes of items.
1560          */
addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser, ArrayList<Long> folderItems, long folderId)1561         private void addToFolder(SQLiteDatabase db, Resources res, XmlResourceParser parser,
1562                 ArrayList<Long> folderItems, long folderId) throws IOException, XmlPullParserException {
1563             int type;
1564             int folderDepth = parser.getDepth();
1565             while ((type = parser.next()) != XmlPullParser.END_TAG ||
1566                     parser.getDepth() > folderDepth) {
1567                 if (type != XmlPullParser.START_TAG) {
1568                     continue;
1569                 }
1570                 final String tag = parser.getName();
1571 
1572                 final ContentValues childValues = new ContentValues();
1573                 childValues.put(LauncherSettings.Favorites.CONTAINER, folderId);
1574 
1575                 if (LOGD) {
1576                     final String pkg = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1577                     final String uri = getAttributeValue(parser, ATTR_URI);
1578                     Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
1579                             tag, uri != null ? uri : pkg));
1580                 }
1581 
1582                 if (TAG_FAVORITE.equals(tag) && folderId >= 0) {
1583                     final long id = addAppShortcut(db, childValues, parser);
1584                     if (id >= 0) {
1585                         folderItems.add(id);
1586                     }
1587                 } else if (TAG_SHORTCUT.equals(tag) && folderId >= 0) {
1588                     final long id = addUriShortcut(db, childValues, res, parser);
1589                     if (id >= 0) {
1590                         folderItems.add(id);
1591                     }
1592                 } else if (TAG_INCLUDE.equals(tag) && folderId >= 0) {
1593                     addToFolder(db, res, parser, folderItems, folderId);
1594                 } else {
1595                     throw new RuntimeException("Folders can contain only shortcuts");
1596                 }
1597             }
1598         }
1599 
1600         /**
1601          * Parse folder starting at current {@link XmlPullParser} location.
1602          */
loadFolder(SQLiteDatabase db, ContentValues values, Resources res, XmlResourceParser parser)1603         private boolean loadFolder(SQLiteDatabase db, ContentValues values, Resources res,
1604                 XmlResourceParser parser) throws IOException, XmlPullParserException {
1605             final String title;
1606             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
1607             if (titleResId != 0) {
1608                 title = res.getString(titleResId);
1609             } else {
1610                 title = mContext.getResources().getString(R.string.folder_name);
1611             }
1612 
1613             values.put(LauncherSettings.Favorites.TITLE, title);
1614             long folderId = addFolder(db, values);
1615             boolean added = folderId >= 0;
1616 
1617             ArrayList<Long> folderItems = new ArrayList<Long>();
1618             addToFolder(db, res, parser, folderItems, folderId);
1619 
1620             // We can only have folders with >= 2 items, so we need to remove the
1621             // folder and clean up if less than 2 items were included, or some
1622             // failed to add, and less than 2 were actually added
1623             if (folderItems.size() < 2 && folderId >= 0) {
1624                 // Delete the folder
1625                 deleteId(db, folderId);
1626 
1627                 // If we have a single item, promote it to where the folder
1628                 // would have been.
1629                 if (folderItems.size() == 1) {
1630                     final ContentValues childValues = new ContentValues();
1631                     copyInteger(values, childValues, LauncherSettings.Favorites.CONTAINER);
1632                     copyInteger(values, childValues, LauncherSettings.Favorites.SCREEN);
1633                     copyInteger(values, childValues, LauncherSettings.Favorites.CELLX);
1634                     copyInteger(values, childValues, LauncherSettings.Favorites.CELLY);
1635 
1636                     final long id = folderItems.get(0);
1637                     db.update(TABLE_FAVORITES, childValues,
1638                             LauncherSettings.Favorites._ID + "=" + id, null);
1639                 } else {
1640                     added = false;
1641                 }
1642             }
1643             return added;
1644         }
1645 
1646         // A meta shortcut attempts to resolve an intent specified as a URI in the XML, if a
1647         // logical choice for what shortcut should be used for that intent exists, then it is
1648         // added. Otherwise add nothing.
addAppShortcutByUri(SQLiteDatabase db, ContentValues values, String intentUri)1649         private long addAppShortcutByUri(SQLiteDatabase db, ContentValues values,
1650                 String intentUri) {
1651             Intent metaIntent;
1652             try {
1653                 metaIntent = Intent.parseUri(intentUri, 0);
1654             } catch (URISyntaxException e) {
1655                 Log.e(TAG, "Unable to add meta-favorite: " + intentUri, e);
1656                 return -1;
1657             }
1658 
1659             ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
1660                     PackageManager.MATCH_DEFAULT_ONLY);
1661             final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
1662                     metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
1663 
1664             // Verify that the result is an app and not just the resolver dialog asking which
1665             // app to use.
1666             if (wouldLaunchResolverActivity(resolved, appList)) {
1667                 // If only one of the results is a system app then choose that as the default.
1668                 final ResolveInfo systemApp = getSingleSystemActivity(appList);
1669                 if (systemApp == null) {
1670                     // There is no logical choice for this meta-favorite, so rather than making
1671                     // a bad choice just add nothing.
1672                     Log.w(TAG, "No preference or single system activity found for "
1673                             + metaIntent.toString());
1674                     return -1;
1675                 }
1676                 resolved = systemApp;
1677             }
1678             final ActivityInfo info = resolved.activityInfo;
1679             final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
1680             if (intent == null) {
1681                 return -1;
1682             }
1683             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1684                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1685 
1686             return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(), intent);
1687         }
1688 
getSingleSystemActivity(List<ResolveInfo> appList)1689         private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
1690             ResolveInfo systemResolve = null;
1691             final int N = appList.size();
1692             for (int i = 0; i < N; ++i) {
1693                 try {
1694                     ApplicationInfo info = mPackageManager.getApplicationInfo(
1695                             appList.get(i).activityInfo.packageName, 0);
1696                     if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
1697                         if (systemResolve != null) {
1698                             return null;
1699                         } else {
1700                             systemResolve = appList.get(i);
1701                         }
1702                     }
1703                 } catch (PackageManager.NameNotFoundException e) {
1704                     Log.w(TAG, "Unable to get info about resolve results", e);
1705                     return null;
1706                 }
1707             }
1708             return systemResolve;
1709         }
1710 
wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)1711         private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
1712                 List<ResolveInfo> appList) {
1713             // If the list contains the above resolved activity, then it can't be
1714             // ResolverActivity itself.
1715             for (int i = 0; i < appList.size(); ++i) {
1716                 ResolveInfo tmp = appList.get(i);
1717                 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
1718                         && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
1719                     return false;
1720                 }
1721             }
1722             return true;
1723         }
1724 
addAppShortcut(SQLiteDatabase db, ContentValues values, XmlResourceParser parser)1725         private long addAppShortcut(SQLiteDatabase db, ContentValues values,
1726                 XmlResourceParser parser) {
1727             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1728             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
1729             final String uri = getAttributeValue(parser, ATTR_URI);
1730 
1731             if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
1732                 ActivityInfo info;
1733                 try {
1734                     ComponentName cn;
1735                     try {
1736                         cn = new ComponentName(packageName, className);
1737                         info = mPackageManager.getActivityInfo(cn, 0);
1738                     } catch (PackageManager.NameNotFoundException nnfe) {
1739                         String[] packages = mPackageManager.currentToCanonicalPackageNames(
1740                                 new String[] { packageName });
1741                         cn = new ComponentName(packages[0], className);
1742                         info = mPackageManager.getActivityInfo(cn, 0);
1743                     }
1744                     final Intent intent = buildMainIntent();
1745                     intent.setComponent(cn);
1746                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1747                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1748 
1749                     return addAppShortcut(db, values, info.loadLabel(mPackageManager).toString(),
1750                             intent);
1751                 } catch (PackageManager.NameNotFoundException e) {
1752                     Log.w(TAG, "Unable to add favorite: " + packageName +
1753                             "/" + className, e);
1754                 }
1755                 return -1;
1756             } else if (!TextUtils.isEmpty(uri)) {
1757                 // If no component specified try to find a shortcut to add from the URI.
1758                 return addAppShortcutByUri(db, values, uri);
1759             } else {
1760                 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
1761                 return -1;
1762             }
1763         }
1764 
addAppShortcut(SQLiteDatabase db, ContentValues values, String title, Intent intent)1765         private long addAppShortcut(SQLiteDatabase db, ContentValues values, String title,
1766                 Intent intent) {
1767             long id = generateNewItemId();
1768             values.put(Favorites.INTENT, intent.toUri(0));
1769             values.put(Favorites.TITLE, title);
1770             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
1771             values.put(Favorites.SPANX, 1);
1772             values.put(Favorites.SPANY, 1);
1773             values.put(Favorites._ID, id);
1774             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1775                 return -1;
1776             } else {
1777                 return id;
1778             }
1779         }
1780 
addFolder(SQLiteDatabase db, ContentValues values)1781         private long addFolder(SQLiteDatabase db, ContentValues values) {
1782             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
1783             values.put(Favorites.SPANX, 1);
1784             values.put(Favorites.SPANY, 1);
1785             long id = generateNewItemId();
1786             values.put(Favorites._ID, id);
1787             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
1788                 return -1;
1789             } else {
1790                 return id;
1791             }
1792         }
1793 
getSearchWidgetProvider()1794         private ComponentName getSearchWidgetProvider() {
1795             SearchManager searchManager =
1796                     (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
1797             ComponentName searchComponent = searchManager.getGlobalSearchActivity();
1798             if (searchComponent == null) return null;
1799             return getProviderInPackage(searchComponent.getPackageName());
1800         }
1801 
1802         /**
1803          * Gets an appwidget provider from the given package. If the package contains more than
1804          * one appwidget provider, an arbitrary one is returned.
1805          */
getProviderInPackage(String packageName)1806         private ComponentName getProviderInPackage(String packageName) {
1807             AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1808             List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
1809             if (providers == null) return null;
1810             final int providerCount = providers.size();
1811             for (int i = 0; i < providerCount; i++) {
1812                 ComponentName provider = providers.get(i).provider;
1813                 if (provider != null && provider.getPackageName().equals(packageName)) {
1814                     return provider;
1815                 }
1816             }
1817             return null;
1818         }
1819 
addAppWidget(XmlResourceParser parser, int type, SQLiteDatabase db, ContentValues values)1820         private boolean addAppWidget(XmlResourceParser parser, int type,
1821                 SQLiteDatabase db, ContentValues values)
1822                 throws XmlPullParserException, IOException {
1823 
1824             String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
1825             String className = getAttributeValue(parser, ATTR_CLASS_NAME);
1826 
1827             if (packageName == null || className == null) {
1828                 return false;
1829             }
1830 
1831             boolean hasPackage = true;
1832             ComponentName cn = new ComponentName(packageName, className);
1833             try {
1834                 mPackageManager.getReceiverInfo(cn, 0);
1835             } catch (Exception e) {
1836                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
1837                         new String[] { packageName });
1838                 cn = new ComponentName(packages[0], className);
1839                 try {
1840                     mPackageManager.getReceiverInfo(cn, 0);
1841                 } catch (Exception e1) {
1842                     System.out.println("Can't find widget provider: " + className);
1843                     hasPackage = false;
1844                 }
1845             }
1846 
1847             if (hasPackage) {
1848                 String spanX = getAttributeValue(parser, ATTR_SPAN_X);
1849                 String spanY = getAttributeValue(parser, ATTR_SPAN_Y);
1850 
1851                 values.put(Favorites.SPANX, spanX);
1852                 values.put(Favorites.SPANY, spanY);
1853 
1854                 // Read the extras
1855                 Bundle extras = new Bundle();
1856                 int widgetDepth = parser.getDepth();
1857                 while ((type = parser.next()) != XmlPullParser.END_TAG ||
1858                         parser.getDepth() > widgetDepth) {
1859                     if (type != XmlPullParser.START_TAG) {
1860                         continue;
1861                     }
1862 
1863                     if (TAG_EXTRA.equals(parser.getName())) {
1864                         String key = getAttributeValue(parser, ATTR_KEY);
1865                         String value = getAttributeValue(parser, ATTR_VALUE);
1866                         if (key != null && value != null) {
1867                             extras.putString(key, value);
1868                         } else {
1869                             throw new RuntimeException("Widget extras must have a key and value");
1870                         }
1871                     } else {
1872                         throw new RuntimeException("Widgets can contain only extras");
1873                     }
1874                 }
1875 
1876                 return addAppWidget(db, values, cn, extras);
1877             }
1878 
1879             return false;
1880         }
1881 
addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn, Bundle extras)1882         private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
1883                Bundle extras) {
1884             boolean allocatedAppWidgets = false;
1885             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
1886 
1887             try {
1888                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
1889 
1890                 values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
1891                 values.put(Favorites.APPWIDGET_ID, appWidgetId);
1892                 values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
1893                 values.put(Favorites._ID, generateNewItemId());
1894                 dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
1895 
1896                 allocatedAppWidgets = true;
1897 
1898                 // TODO: need to check return value
1899                 appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
1900 
1901                 // Send a broadcast to configure the widget
1902                 if (extras != null && !extras.isEmpty()) {
1903                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
1904                     intent.setComponent(cn);
1905                     intent.putExtras(extras);
1906                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
1907                     mContext.sendBroadcast(intent);
1908                 }
1909             } catch (RuntimeException ex) {
1910                 Log.e(TAG, "Problem allocating appWidgetId", ex);
1911             }
1912 
1913             return allocatedAppWidgets;
1914         }
1915 
addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res, XmlResourceParser parser)1916         private long addUriShortcut(SQLiteDatabase db, ContentValues values, Resources res,
1917                 XmlResourceParser parser) {
1918             final int iconResId = getAttributeResourceValue(parser, ATTR_ICON, 0);
1919             final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
1920 
1921             Intent intent;
1922             String uri = null;
1923             try {
1924                 uri = getAttributeValue(parser, ATTR_URI);
1925                 intent = Intent.parseUri(uri, 0);
1926             } catch (URISyntaxException e) {
1927                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
1928                 return -1; // Oh well
1929             }
1930 
1931             if (iconResId == 0 || titleResId == 0) {
1932                 Log.w(TAG, "Shortcut is missing title or icon resource ID");
1933                 return -1;
1934             }
1935 
1936             long id = generateNewItemId();
1937             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1938             values.put(Favorites.INTENT, intent.toUri(0));
1939             values.put(Favorites.TITLE, res.getString(titleResId));
1940             values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
1941             values.put(Favorites.SPANX, 1);
1942             values.put(Favorites.SPANY, 1);
1943             values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
1944             values.put(Favorites.ICON_PACKAGE, res.getResourcePackageName(iconResId));
1945             values.put(Favorites.ICON_RESOURCE, res.getResourceName(iconResId));
1946             values.put(Favorites._ID, id);
1947 
1948             if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
1949                 return -1;
1950             }
1951             return id;
1952         }
1953 
migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri)1954         private void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) {
1955             final ContentResolver resolver = mContext.getContentResolver();
1956             Cursor c = null;
1957             int count = 0;
1958             int curScreen = 0;
1959 
1960             try {
1961                 c = resolver.query(uri, null, null, null, "title ASC");
1962             } catch (Exception e) {
1963                 // Ignore
1964             }
1965 
1966             // We already have a favorites database in the old provider
1967             if (c != null) {
1968                 try {
1969                     if (c.getCount() > 0) {
1970                         final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1971                         final int intentIndex
1972                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1973                         final int titleIndex
1974                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1975                         final int iconTypeIndex
1976                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
1977                         final int iconIndex
1978                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1979                         final int iconPackageIndex
1980                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
1981                         final int iconResourceIndex
1982                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
1983                         final int containerIndex
1984                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1985                         final int itemTypeIndex
1986                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1987                         final int screenIndex
1988                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1989                         final int cellXIndex
1990                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1991                         final int cellYIndex
1992                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1993                         final int uriIndex
1994                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1995                         final int displayModeIndex
1996                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
1997                         final int profileIndex
1998                                 = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
1999 
2000                         int i = 0;
2001                         int curX = 0;
2002                         int curY = 0;
2003 
2004                         final LauncherAppState app = LauncherAppState.getInstance();
2005                         final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2006                         final int width = (int) grid.numColumns;
2007                         final int height = (int) grid.numRows;
2008                         final int hotseatWidth = (int) grid.numHotseatIcons;
2009 
2010                         final HashSet<String> seenIntents = new HashSet<String>(c.getCount());
2011 
2012                         final ArrayList<ContentValues> shortcuts = new ArrayList<ContentValues>();
2013                         final ArrayList<ContentValues> folders = new ArrayList<ContentValues>();
2014                         final SparseArray<ContentValues> hotseat = new SparseArray<ContentValues>();
2015 
2016                         while (c.moveToNext()) {
2017                             final int itemType = c.getInt(itemTypeIndex);
2018                             if (itemType != Favorites.ITEM_TYPE_APPLICATION
2019                                     && itemType != Favorites.ITEM_TYPE_SHORTCUT
2020                                     && itemType != Favorites.ITEM_TYPE_FOLDER) {
2021                                 continue;
2022                             }
2023 
2024                             final int cellX = c.getInt(cellXIndex);
2025                             final int cellY = c.getInt(cellYIndex);
2026                             final int screen = c.getInt(screenIndex);
2027                             int container = c.getInt(containerIndex);
2028                             final String intentStr = c.getString(intentIndex);
2029 
2030                             UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
2031                             UserHandleCompat userHandle;
2032                             final long userSerialNumber;
2033                             if (profileIndex != -1 && !c.isNull(profileIndex)) {
2034                                 userSerialNumber = c.getInt(profileIndex);
2035                                 userHandle = userManager.getUserForSerialNumber(userSerialNumber);
2036                             } else {
2037                                 // Default to the serial number of this user, for older
2038                                 // shortcuts.
2039                                 userHandle = UserHandleCompat.myUserHandle();
2040                                 userSerialNumber = userManager.getSerialNumberForUser(userHandle);
2041                             }
2042                             Launcher.addDumpLog(TAG, "migrating \""
2043                                 + c.getString(titleIndex) + "\" ("
2044                                 + cellX + "," + cellY + "@"
2045                                 + LauncherSettings.Favorites.containerToString(container)
2046                                 + "/" + screen
2047                                 + "): " + intentStr, true);
2048 
2049                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
2050 
2051                                 final Intent intent;
2052                                 final ComponentName cn;
2053                                 try {
2054                                     intent = Intent.parseUri(intentStr, 0);
2055                                 } catch (URISyntaxException e) {
2056                                     // bogus intent?
2057                                     Launcher.addDumpLog(TAG,
2058                                             "skipping invalid intent uri", true);
2059                                     continue;
2060                                 }
2061 
2062                                 cn = intent.getComponent();
2063                                 if (TextUtils.isEmpty(intentStr)) {
2064                                     // no intent? no icon
2065                                     Launcher.addDumpLog(TAG, "skipping empty intent", true);
2066                                     continue;
2067                                 } else if (cn != null &&
2068                                         !LauncherModel.isValidPackageActivity(mContext, cn,
2069                                                 userHandle)) {
2070                                     // component no longer exists.
2071                                     Launcher.addDumpLog(TAG, "skipping item whose component " +
2072                                             "no longer exists.", true);
2073                                     continue;
2074                                 } else if (container ==
2075                                         LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2076                                     // Dedupe icons directly on the workspace
2077 
2078                                     // Canonicalize
2079                                     // the Play Store sets the package parameter, but Launcher
2080                                     // does not, so we clear that out to keep them the same
2081                                     intent.setPackage(null);
2082                                     final String key = intent.toUri(0);
2083                                     if (seenIntents.contains(key)) {
2084                                         Launcher.addDumpLog(TAG, "skipping duplicate", true);
2085                                         continue;
2086                                     } else {
2087                                         seenIntents.add(key);
2088                                     }
2089                                 }
2090                             }
2091 
2092                             ContentValues values = new ContentValues(c.getColumnCount());
2093                             values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex));
2094                             values.put(LauncherSettings.Favorites.INTENT, intentStr);
2095                             values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
2096                             values.put(LauncherSettings.Favorites.ICON_TYPE,
2097                                     c.getInt(iconTypeIndex));
2098                             values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
2099                             values.put(LauncherSettings.Favorites.ICON_PACKAGE,
2100                                     c.getString(iconPackageIndex));
2101                             values.put(LauncherSettings.Favorites.ICON_RESOURCE,
2102                                     c.getString(iconResourceIndex));
2103                             values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
2104                             values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
2105                             values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
2106                             values.put(LauncherSettings.Favorites.DISPLAY_MODE,
2107                                     c.getInt(displayModeIndex));
2108                             values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
2109 
2110                             if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2111                                 hotseat.put(screen, values);
2112                             }
2113 
2114                             if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2115                                 // In a folder or in the hotseat, preserve position
2116                                 values.put(LauncherSettings.Favorites.SCREEN, screen);
2117                                 values.put(LauncherSettings.Favorites.CELLX, cellX);
2118                                 values.put(LauncherSettings.Favorites.CELLY, cellY);
2119                             } else {
2120                                 // For items contained directly on one of the workspace screen,
2121                                 // we'll determine their location (screen, x, y) in a second pass.
2122                             }
2123 
2124                             values.put(LauncherSettings.Favorites.CONTAINER, container);
2125 
2126                             if (itemType != Favorites.ITEM_TYPE_FOLDER) {
2127                                 shortcuts.add(values);
2128                             } else {
2129                                 folders.add(values);
2130                             }
2131                         }
2132 
2133                         // Now that we have all the hotseat icons, let's go through them left-right
2134                         // and assign valid locations for them in the new hotseat
2135                         final int N = hotseat.size();
2136                         for (int idx=0; idx<N; idx++) {
2137                             int hotseatX = hotseat.keyAt(idx);
2138                             ContentValues values = hotseat.valueAt(idx);
2139 
2140                             if (hotseatX == grid.hotseatAllAppsRank) {
2141                                 // let's drop this in the next available hole in the hotseat
2142                                 while (++hotseatX < hotseatWidth) {
2143                                     if (hotseat.get(hotseatX) == null) {
2144                                         // found a spot! move it here
2145                                         values.put(LauncherSettings.Favorites.SCREEN,
2146                                                 hotseatX);
2147                                         break;
2148                                     }
2149                                 }
2150                             }
2151                             if (hotseatX >= hotseatWidth) {
2152                                 // no room for you in the hotseat? it's off to the desktop with you
2153                                 values.put(LauncherSettings.Favorites.CONTAINER,
2154                                            Favorites.CONTAINER_DESKTOP);
2155                             }
2156                         }
2157 
2158                         final ArrayList<ContentValues> allItems = new ArrayList<ContentValues>();
2159                         // Folders first
2160                         allItems.addAll(folders);
2161                         // Then shortcuts
2162                         allItems.addAll(shortcuts);
2163 
2164                         // Layout all the folders
2165                         for (ContentValues values: allItems) {
2166                             if (values.getAsInteger(LauncherSettings.Favorites.CONTAINER) !=
2167                                     LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2168                                 // Hotseat items and folder items have already had their
2169                                 // location information set. Nothing to be done here.
2170                                 continue;
2171                             }
2172                             values.put(LauncherSettings.Favorites.SCREEN, curScreen);
2173                             values.put(LauncherSettings.Favorites.CELLX, curX);
2174                             values.put(LauncherSettings.Favorites.CELLY, curY);
2175                             curX = (curX + 1) % width;
2176                             if (curX == 0) {
2177                                 curY = (curY + 1);
2178                             }
2179                             // Leave the last row of icons blank on every screen
2180                             if (curY == height - 1) {
2181                                 curScreen = (int) generateNewScreenId();
2182                                 curY = 0;
2183                             }
2184                         }
2185 
2186                         if (allItems.size() > 0) {
2187                             db.beginTransaction();
2188                             try {
2189                                 for (ContentValues row: allItems) {
2190                                     if (row == null) continue;
2191                                     if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, row)
2192                                             < 0) {
2193                                         return;
2194                                     } else {
2195                                         count++;
2196                                     }
2197                                 }
2198                                 db.setTransactionSuccessful();
2199                             } finally {
2200                                 db.endTransaction();
2201                             }
2202                         }
2203 
2204                         db.beginTransaction();
2205                         try {
2206                             for (i=0; i<=curScreen; i++) {
2207                                 final ContentValues values = new ContentValues();
2208                                 values.put(LauncherSettings.WorkspaceScreens._ID, i);
2209                                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
2210                                 if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values)
2211                                         < 0) {
2212                                     return;
2213                                 }
2214                             }
2215                             db.setTransactionSuccessful();
2216                         } finally {
2217                             db.endTransaction();
2218                         }
2219                     }
2220                 } finally {
2221                     c.close();
2222                 }
2223             }
2224 
2225             Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into "
2226                     + (curScreen+1) + " screens", true);
2227 
2228             // ensure that new screens are created to hold these icons
2229             setFlagJustLoadedOldDb();
2230 
2231             // Update max IDs; very important since we just grabbed IDs from another database
2232             mMaxItemId = initializeMaxItemId(db);
2233             mMaxScreenId = initializeMaxScreenId(db);
2234             if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId);
2235         }
2236     }
2237 
2238     /**
2239      * Build a query string that will match any row where the column matches
2240      * anything in the values list.
2241      */
buildOrWhereString(String column, int[] values)2242     private static String buildOrWhereString(String column, int[] values) {
2243         StringBuilder selectWhere = new StringBuilder();
2244         for (int i = values.length - 1; i >= 0; i--) {
2245             selectWhere.append(column).append("=").append(values[i]);
2246             if (i > 0) {
2247                 selectWhere.append(" OR ");
2248             }
2249         }
2250         return selectWhere.toString();
2251     }
2252 
2253     /**
2254      * Return attribute value, attempting launcher-specific namespace first
2255      * before falling back to anonymous attribute.
2256      */
getAttributeValue(XmlResourceParser parser, String attribute)2257     private static String getAttributeValue(XmlResourceParser parser, String attribute) {
2258         String value = parser.getAttributeValue(
2259                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
2260         if (value == null) {
2261             value = parser.getAttributeValue(null, attribute);
2262         }
2263         return value;
2264     }
2265 
2266     /**
2267      * Return attribute resource value, attempting launcher-specific namespace
2268      * first before falling back to anonymous attribute.
2269      */
getAttributeResourceValue(XmlResourceParser parser, String attribute, int defaultValue)2270     private static int getAttributeResourceValue(XmlResourceParser parser, String attribute,
2271             int defaultValue) {
2272         int value = parser.getAttributeResourceValue(
2273                 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
2274                 defaultValue);
2275         if (value == defaultValue) {
2276             value = parser.getAttributeResourceValue(null, attribute, defaultValue);
2277         }
2278         return value;
2279     }
2280 
copyInteger(ContentValues from, ContentValues to, String key)2281     private static void copyInteger(ContentValues from, ContentValues to, String key) {
2282         to.put(key, from.getAsInteger(key));
2283     }
2284 
2285     static class SqlArguments {
2286         public final String table;
2287         public final String where;
2288         public final String[] args;
2289 
SqlArguments(Uri url, String where, String[] args)2290         SqlArguments(Uri url, String where, String[] args) {
2291             if (url.getPathSegments().size() == 1) {
2292                 this.table = url.getPathSegments().get(0);
2293                 this.where = where;
2294                 this.args = args;
2295             } else if (url.getPathSegments().size() != 2) {
2296                 throw new IllegalArgumentException("Invalid URI: " + url);
2297             } else if (!TextUtils.isEmpty(where)) {
2298                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
2299             } else {
2300                 this.table = url.getPathSegments().get(0);
2301                 this.where = "_id=" + ContentUris.parseId(url);
2302                 this.args = null;
2303             }
2304         }
2305 
SqlArguments(Uri url)2306         SqlArguments(Uri url) {
2307             if (url.getPathSegments().size() == 1) {
2308                 table = url.getPathSegments().get(0);
2309                 where = null;
2310                 args = null;
2311             } else {
2312                 throw new IllegalArgumentException("Invalid URI: " + url);
2313             }
2314         }
2315     }
2316 
2317     static interface WorkspaceLoader {
2318         /**
2319          * @param screenIds A mutable list of screen its
2320          * @return the number of workspace items added.
2321          */
loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds)2322         int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds);
2323     }
2324 
2325     private static class SimpleWorkspaceLoader implements WorkspaceLoader {
2326         private final Resources mRes;
2327         private final int mWorkspaceId;
2328         private final DatabaseHelper mHelper;
2329 
SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId)2330         SimpleWorkspaceLoader(DatabaseHelper helper, Resources res, int workspaceId) {
2331             mHelper = helper;
2332             mRes = res;
2333             mWorkspaceId = workspaceId;
2334         }
2335 
2336         @Override
loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds)2337         public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
2338             return mHelper.loadFavoritesRecursive(db, mRes, mWorkspaceId, screenIds);
2339         }
2340     }
2341 }
2342