• 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.annotation.TargetApi;
20 import android.appwidget.AppWidgetHost;
21 import android.appwidget.AppWidgetManager;
22 import android.content.ComponentName;
23 import android.content.ContentProvider;
24 import android.content.ContentProviderOperation;
25 import android.content.ContentProviderResult;
26 import android.content.ContentUris;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.OperationApplicationException;
31 import android.content.SharedPreferences;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.res.Resources;
34 import android.database.Cursor;
35 import android.database.SQLException;
36 import android.database.sqlite.SQLiteDatabase;
37 import android.database.sqlite.SQLiteQueryBuilder;
38 import android.database.sqlite.SQLiteStatement;
39 import android.net.Uri;
40 import android.os.Binder;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.os.Message;
45 import android.os.Process;
46 import android.os.UserHandle;
47 import android.os.UserManager;
48 import android.text.TextUtils;
49 import android.util.Log;
50 
51 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
52 import com.android.launcher3.LauncherSettings.Favorites;
53 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
54 import com.android.launcher3.compat.UserManagerCompat;
55 import com.android.launcher3.config.FeatureFlags;
56 import com.android.launcher3.logging.FileLog;
57 import com.android.launcher3.model.DbDowngradeHelper;
58 import com.android.launcher3.provider.LauncherDbUtils;
59 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
60 import com.android.launcher3.provider.RestoreDbTask;
61 import com.android.launcher3.util.NoLocaleSQLiteHelper;
62 import com.android.launcher3.util.Preconditions;
63 import com.android.launcher3.util.Thunk;
64 
65 import java.io.File;
66 import java.io.FileDescriptor;
67 import java.io.PrintWriter;
68 import java.net.URISyntaxException;
69 import java.util.ArrayList;
70 import java.util.Collections;
71 import java.util.HashSet;
72 import java.util.LinkedHashSet;
73 
74 public class LauncherProvider extends ContentProvider {
75     private static final String TAG = "LauncherProvider";
76     private static final boolean LOGD = false;
77 
78     private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
79 
80     /**
81      * Represents the schema of the database. Changes in scheme need not be backwards compatible.
82      */
83     public static final int SCHEMA_VERSION = 27;
84 
85     public static final String AUTHORITY = FeatureFlags.AUTHORITY;
86 
87     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
88 
89     private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
90 
91     private final ChangeListenerWrapper mListenerWrapper = new ChangeListenerWrapper();
92     private Handler mListenerHandler;
93 
94     protected DatabaseHelper mOpenHelper;
95 
96     /**
97      * $ adb shell dumpsys activity provider com.android.launcher3
98      */
99     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)100     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
101         LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
102         if (appState == null || !appState.getModel().isModelLoaded()) {
103             return;
104         }
105         appState.getModel().dumpState("", fd, writer, args);
106     }
107 
108     @Override
onCreate()109     public boolean onCreate() {
110         if (FeatureFlags.IS_DOGFOOD_BUILD) {
111             Log.d(TAG, "Launcher process started");
112         }
113         mListenerHandler = new Handler(mListenerWrapper);
114 
115         // The content provider exists for the entire duration of the launcher main process and
116         // is the first component to get created.
117         MainProcessInitializer.initialize(getContext().getApplicationContext());
118         return true;
119     }
120 
121     /**
122      * Sets a provider listener.
123      */
setLauncherProviderChangeListener(LauncherProviderChangeListener listener)124     public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
125         Preconditions.assertUIThread();
126         mListenerWrapper.mListener = listener;
127     }
128 
129     @Override
getType(Uri uri)130     public String getType(Uri uri) {
131         SqlArguments args = new SqlArguments(uri, null, null);
132         if (TextUtils.isEmpty(args.where)) {
133             return "vnd.android.cursor.dir/" + args.table;
134         } else {
135             return "vnd.android.cursor.item/" + args.table;
136         }
137     }
138 
139     /**
140      * Overridden in tests
141      */
createDbIfNotExists()142     protected synchronized void createDbIfNotExists() {
143         if (mOpenHelper == null) {
144             mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
145 
146             if (RestoreDbTask.isPending(getContext())) {
147                 if (!RestoreDbTask.performRestore(mOpenHelper)) {
148                     mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
149                 }
150                 // Set is pending to false irrespective of the result, so that it doesn't get
151                 // executed again.
152                 RestoreDbTask.setPending(getContext(), false);
153             }
154         }
155     }
156 
157     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)158     public Cursor query(Uri uri, String[] projection, String selection,
159             String[] selectionArgs, String sortOrder) {
160         createDbIfNotExists();
161 
162         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
163         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
164         qb.setTables(args.table);
165 
166         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
167         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
168         result.setNotificationUri(getContext().getContentResolver(), uri);
169 
170         return result;
171     }
172 
dbInsertAndCheck(DatabaseHelper helper, SQLiteDatabase db, String table, String nullColumnHack, ContentValues values)173     @Thunk static long dbInsertAndCheck(DatabaseHelper helper,
174             SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
175         if (values == null) {
176             throw new RuntimeException("Error: attempting to insert null values");
177         }
178         if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
179             throw new RuntimeException("Error: attempting to add item without specifying an id");
180         }
181         helper.checkId(table, values);
182         return db.insert(table, nullColumnHack, values);
183     }
184 
reloadLauncherIfExternal()185     private void reloadLauncherIfExternal() {
186         if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
187             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
188             if (app != null) {
189                 app.getModel().forceReload();
190             }
191         }
192     }
193 
194     @Override
insert(Uri uri, ContentValues initialValues)195     public Uri insert(Uri uri, ContentValues initialValues) {
196         createDbIfNotExists();
197         SqlArguments args = new SqlArguments(uri);
198 
199         // In very limited cases, we support system|signature permission apps to modify the db.
200         if (Binder.getCallingPid() != Process.myPid()) {
201             if (!initializeExternalAdd(initialValues)) {
202                 return null;
203             }
204         }
205 
206         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
207         addModifiedTime(initialValues);
208         final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
209         if (rowId < 0) return null;
210 
211         uri = ContentUris.withAppendedId(uri, rowId);
212         notifyListeners();
213 
214         if (Utilities.ATLEAST_MARSHMALLOW) {
215             reloadLauncherIfExternal();
216         } else {
217             // Deprecated behavior to support legacy devices which rely on provider callbacks.
218             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
219             if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
220                 app.getModel().forceReload();
221             }
222 
223             String notify = uri.getQueryParameter("notify");
224             if (notify == null || "true".equals(notify)) {
225                 getContext().getContentResolver().notifyChange(uri, null);
226             }
227         }
228         return uri;
229     }
230 
initializeExternalAdd(ContentValues values)231     private boolean initializeExternalAdd(ContentValues values) {
232         // 1. Ensure that externally added items have a valid item id
233         long id = mOpenHelper.generateNewItemId();
234         values.put(LauncherSettings.Favorites._ID, id);
235 
236         // 2. In the case of an app widget, and if no app widget id is specified, we
237         // attempt allocate and bind the widget.
238         Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
239         if (itemType != null &&
240                 itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
241                 !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
242 
243             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
244             ComponentName cn = ComponentName.unflattenFromString(
245                     values.getAsString(Favorites.APPWIDGET_PROVIDER));
246 
247             if (cn != null) {
248                 try {
249                     AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
250                     int appWidgetId = widgetHost.allocateAppWidgetId();
251                     values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
252                     if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
253                         widgetHost.deleteAppWidgetId(appWidgetId);
254                         return false;
255                     }
256                 } catch (RuntimeException e) {
257                     Log.e(TAG, "Failed to initialize external widget", e);
258                     return false;
259                 }
260             } else {
261                 return false;
262             }
263         }
264 
265         // Add screen id if not present
266         long screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN);
267         SQLiteStatement stmp = null;
268         try {
269             stmp = mOpenHelper.getWritableDatabase().compileStatement(
270                     "INSERT OR IGNORE INTO workspaceScreens (_id, screenRank) " +
271                             "select ?, (ifnull(MAX(screenRank), -1)+1) from workspaceScreens");
272             stmp.bindLong(1, screenId);
273 
274             ContentValues valuesInserted = new ContentValues();
275             valuesInserted.put(LauncherSettings.BaseLauncherColumns._ID, stmp.executeInsert());
276             mOpenHelper.checkId(WorkspaceScreens.TABLE_NAME, valuesInserted);
277             return true;
278         } catch (Exception e) {
279             return false;
280         } finally {
281             Utilities.closeSilently(stmp);
282         }
283     }
284 
285     @Override
bulkInsert(Uri uri, ContentValues[] values)286     public int bulkInsert(Uri uri, ContentValues[] values) {
287         createDbIfNotExists();
288         SqlArguments args = new SqlArguments(uri);
289 
290         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
291         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
292             int numValues = values.length;
293             for (int i = 0; i < numValues; i++) {
294                 addModifiedTime(values[i]);
295                 if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
296                     return 0;
297                 }
298             }
299             t.commit();
300         }
301 
302         notifyListeners();
303         reloadLauncherIfExternal();
304         return values.length;
305     }
306 
307     @Override
applyBatch(ArrayList<ContentProviderOperation> operations)308     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
309             throws OperationApplicationException {
310         createDbIfNotExists();
311         try (SQLiteTransaction t = new SQLiteTransaction(mOpenHelper.getWritableDatabase())) {
312             ContentProviderResult[] result =  super.applyBatch(operations);
313             t.commit();
314             reloadLauncherIfExternal();
315             return result;
316         }
317     }
318 
319     @Override
delete(Uri uri, String selection, String[] selectionArgs)320     public int delete(Uri uri, String selection, String[] selectionArgs) {
321         createDbIfNotExists();
322         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
323 
324         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
325 
326         if (Binder.getCallingPid() != Process.myPid()
327                 && Favorites.TABLE_NAME.equalsIgnoreCase(args.table)) {
328             mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
329         }
330         int count = db.delete(args.table, args.where, args.args);
331         if (count > 0) {
332             notifyListeners();
333             reloadLauncherIfExternal();
334         }
335         return count;
336     }
337 
338     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)339     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
340         createDbIfNotExists();
341         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
342 
343         addModifiedTime(values);
344         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
345         int count = db.update(args.table, values, args.where, args.args);
346         if (count > 0) notifyListeners();
347 
348         reloadLauncherIfExternal();
349         return count;
350     }
351 
352     @Override
call(String method, final String arg, final Bundle extras)353     public Bundle call(String method, final String arg, final Bundle extras) {
354         if (Binder.getCallingUid() != Process.myUid()) {
355             return null;
356         }
357         createDbIfNotExists();
358 
359         switch (method) {
360             case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
361                 clearFlagEmptyDbCreated();
362                 return null;
363             }
364             case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
365                 Bundle result = new Bundle();
366                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
367                         Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
368                 return result;
369             }
370             case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
371                 Bundle result = new Bundle();
372                 result.putSerializable(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders());
373                 return result;
374             }
375             case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
376                 Bundle result = new Bundle();
377                 result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId());
378                 return result;
379             }
380             case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
381                 Bundle result = new Bundle();
382                 result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId());
383                 return result;
384             }
385             case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
386                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
387                 return null;
388             }
389             case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
390                 loadDefaultFavoritesIfNecessary();
391                 return null;
392             }
393             case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
394                 mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
395                 return null;
396             }
397         }
398         return null;
399     }
400 
401     /**
402      * Deletes any empty folder from the DB.
403      * @return Ids of deleted folders.
404      */
deleteEmptyFolders()405     private ArrayList<Long> deleteEmptyFolders() {
406         ArrayList<Long> folderIds = new ArrayList<>();
407         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
408         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
409             // Select folders whose id do not match any container value.
410             String selection = LauncherSettings.Favorites.ITEM_TYPE + " = "
411                     + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND "
412                     + LauncherSettings.Favorites._ID +  " NOT IN (SELECT " +
413                             LauncherSettings.Favorites.CONTAINER + " FROM "
414                                 + Favorites.TABLE_NAME + ")";
415             try (Cursor c = db.query(Favorites.TABLE_NAME,
416                     new String[] {LauncherSettings.Favorites._ID},
417                     selection, null, null, null, null)) {
418                 LauncherDbUtils.iterateCursor(c, 0, folderIds);
419             }
420             if (!folderIds.isEmpty()) {
421                 db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
422                         LauncherSettings.Favorites._ID, folderIds), null);
423             }
424             t.commit();
425         } catch (SQLException ex) {
426             Log.e(TAG, ex.getMessage(), ex);
427             folderIds.clear();
428         }
429         return folderIds;
430     }
431 
432     /**
433      * Overridden in tests
434      */
notifyListeners()435     protected void notifyListeners() {
436         mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_LAUNCHER_PROVIDER_CHANGED);
437     }
438 
addModifiedTime(ContentValues values)439     @Thunk static void addModifiedTime(ContentValues values) {
440         values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
441     }
442 
clearFlagEmptyDbCreated()443     private void clearFlagEmptyDbCreated() {
444         Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
445     }
446 
447     /**
448      * Loads the default workspace based on the following priority scheme:
449      *   1) From the app restrictions
450      *   2) From a package provided by play store
451      *   3) From a partner configuration APK, already in the system image
452      *   4) The default configuration for the particular device
453      */
loadDefaultFavoritesIfNecessary()454     synchronized private void loadDefaultFavoritesIfNecessary() {
455         SharedPreferences sp = Utilities.getPrefs(getContext());
456 
457         if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
458             Log.d(TAG, "loading default workspace");
459 
460             AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
461             AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
462             if (loader == null) {
463                 loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
464             }
465             if (loader == null) {
466                 final Partner partner = Partner.get(getContext().getPackageManager());
467                 if (partner != null && partner.hasDefaultLayout()) {
468                     final Resources partnerRes = partner.getResources();
469                     int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
470                             "xml", partner.getPackageName());
471                     if (workspaceResId != 0) {
472                         loader = new DefaultLayoutParser(getContext(), widgetHost,
473                                 mOpenHelper, partnerRes, workspaceResId);
474                     }
475                 }
476             }
477 
478             final boolean usingExternallyProvidedLayout = loader != null;
479             if (loader == null) {
480                 loader = getDefaultLayoutParser(widgetHost);
481             }
482 
483             // There might be some partially restored DB items, due to buggy restore logic in
484             // previous versions of launcher.
485             mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
486             // Populate favorites table with initial favorites
487             if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
488                     && usingExternallyProvidedLayout) {
489                 // Unable to load external layout. Cleanup and load the internal layout.
490                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
491                 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
492                         getDefaultLayoutParser(widgetHost));
493             }
494             clearFlagEmptyDbCreated();
495         }
496     }
497 
498     /**
499      * Creates workspace loader from an XML resource listed in the app restrictions.
500      *
501      * @return the loader if the restrictions are set and the resource exists; null otherwise.
502      */
createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost)503     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
504         Context ctx = getContext();
505         UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
506         Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
507         if (bundle == null) {
508             return null;
509         }
510 
511         String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME);
512         if (packageName != null) {
513             try {
514                 Resources targetResources = ctx.getPackageManager()
515                         .getResourcesForApplication(packageName);
516                 return AutoInstallsLayout.get(ctx, packageName, targetResources,
517                         widgetHost, mOpenHelper);
518             } catch (NameNotFoundException e) {
519                 Log.e(TAG, "Target package for restricted profile not found", e);
520                 return null;
521             }
522         }
523         return null;
524     }
525 
getDefaultLayoutParser(AppWidgetHost widgetHost)526     private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
527         InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
528         int defaultLayout = idp.defaultLayoutId;
529 
530         UserManagerCompat um = UserManagerCompat.getInstance(getContext());
531         if (um.isDemoUser() && idp.demoModeLayoutId != 0) {
532             defaultLayout = idp.demoModeLayoutId;
533         }
534 
535         return new DefaultLayoutParser(getContext(), widgetHost,
536                 mOpenHelper, getContext().getResources(), defaultLayout);
537     }
538 
539     /**
540      * The class is subclassed in tests to create an in-memory db.
541      */
542     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
543         private final Handler mWidgetHostResetHandler;
544         private final Context mContext;
545         private long mMaxItemId = -1;
546         private long mMaxScreenId = -1;
547 
DatabaseHelper(Context context, Handler widgetHostResetHandler)548         DatabaseHelper(Context context, Handler widgetHostResetHandler) {
549             this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
550             // Table creation sometimes fails silently, which leads to a crash loop.
551             // This way, we will try to create a table every time after crash, so the device
552             // would eventually be able to recover.
553             if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) {
554                 Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
555                 // This operation is a no-op if the table already exists.
556                 addFavoritesTable(getWritableDatabase(), true);
557                 addWorkspacesTable(getWritableDatabase(), true);
558             }
559 
560             initIds();
561         }
562 
563         /**
564          * Constructor used in tests and for restore.
565          */
DatabaseHelper( Context context, Handler widgetHostResetHandler, String tableName)566         public DatabaseHelper(
567                 Context context, Handler widgetHostResetHandler, String tableName) {
568             super(context, tableName, SCHEMA_VERSION);
569             mContext = context;
570             mWidgetHostResetHandler = widgetHostResetHandler;
571         }
572 
initIds()573         protected void initIds() {
574             // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
575             // the DB here
576             if (mMaxItemId == -1) {
577                 mMaxItemId = initializeMaxItemId(getWritableDatabase());
578             }
579             if (mMaxScreenId == -1) {
580                 mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
581             }
582         }
583 
tableExists(String tableName)584         private boolean tableExists(String tableName) {
585             Cursor c = getReadableDatabase().query(
586                     true, "sqlite_master", new String[] {"tbl_name"},
587                     "tbl_name = ?", new String[] {tableName},
588                     null, null, null, null, null);
589             try {
590                 return c.getCount() > 0;
591             } finally {
592                 c.close();
593             }
594         }
595 
596         @Override
onCreate(SQLiteDatabase db)597         public void onCreate(SQLiteDatabase db) {
598             if (LOGD) Log.d(TAG, "creating new launcher database");
599 
600             mMaxItemId = 1;
601             mMaxScreenId = 0;
602 
603             addFavoritesTable(db, false);
604             addWorkspacesTable(db, false);
605 
606             // Fresh and clean launcher DB.
607             mMaxItemId = initializeMaxItemId(db);
608             onEmptyDbCreated();
609         }
610 
611         /**
612          * Overriden in tests.
613          */
onEmptyDbCreated()614         protected void onEmptyDbCreated() {
615             // Database was just created, so wipe any previous widgets
616             if (mWidgetHostResetHandler != null) {
617                 newLauncherWidgetHost().deleteHost();
618                 mWidgetHostResetHandler.sendEmptyMessage(
619                         ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
620             }
621 
622             // Set the flag for empty DB
623             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
624         }
625 
getDefaultUserSerial()626         public long getDefaultUserSerial() {
627             return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
628                     Process.myUserHandle());
629         }
630 
addFavoritesTable(SQLiteDatabase db, boolean optional)631         private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
632             Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
633         }
634 
addWorkspacesTable(SQLiteDatabase db, boolean optional)635         private void addWorkspacesTable(SQLiteDatabase db, boolean optional) {
636             String ifNotExists = optional ? " IF NOT EXISTS " : "";
637             db.execSQL("CREATE TABLE " + ifNotExists + WorkspaceScreens.TABLE_NAME + " (" +
638                     LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," +
639                     LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
640                     LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
641                     ");");
642         }
643 
removeOrphanedItems(SQLiteDatabase db)644         private void removeOrphanedItems(SQLiteDatabase db) {
645             // Delete items directly on the workspace who's screen id doesn't exist
646             //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
647             //   AND container = -100"
648             String removeOrphanedDesktopItems = "DELETE FROM " + Favorites.TABLE_NAME +
649                     " WHERE " +
650                     LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
651                     LauncherSettings.WorkspaceScreens._ID + " FROM " + WorkspaceScreens.TABLE_NAME + ")" +
652                     " AND " +
653                     LauncherSettings.Favorites.CONTAINER + " = " +
654                     LauncherSettings.Favorites.CONTAINER_DESKTOP;
655             db.execSQL(removeOrphanedDesktopItems);
656 
657             // Delete items contained in folders which no longer exist (after above statement)
658             //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
659             //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
660             String removeOrphanedFolderItems = "DELETE FROM " + Favorites.TABLE_NAME +
661                     " WHERE " +
662                     LauncherSettings.Favorites.CONTAINER + " <> " +
663                     LauncherSettings.Favorites.CONTAINER_DESKTOP +
664                     " AND "
665                     + LauncherSettings.Favorites.CONTAINER + " <> " +
666                     LauncherSettings.Favorites.CONTAINER_HOTSEAT +
667                     " AND "
668                     + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
669                     LauncherSettings.Favorites._ID + " FROM " + Favorites.TABLE_NAME +
670                     " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
671                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
672             db.execSQL(removeOrphanedFolderItems);
673         }
674 
675         @Override
onOpen(SQLiteDatabase db)676         public void onOpen(SQLiteDatabase db) {
677             super.onOpen(db);
678 
679             File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE);
680             if (!schemaFile.exists()) {
681                 handleOneTimeDataUpgrade(db);
682             }
683             DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext,
684                     R.raw.downgrade_schema);
685         }
686 
687         /**
688          * One-time data updated before support of onDowngrade was added. This update is backwards
689          * compatible and can safely be run multiple times.
690          * Note: No new logic should be added here after release, as the new logic might not get
691          * executed on an existing device.
692          * TODO: Move this to db upgrade path, once the downgrade path is released.
693          */
handleOneTimeDataUpgrade(SQLiteDatabase db)694         protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
695             // Remove "profile extra"
696             UserManagerCompat um = UserManagerCompat.getInstance(mContext);
697             for (UserHandle user : um.getUserProfiles()) {
698                 long serial = um.getSerialNumberForUser(user);
699                 String sql = "update favorites set intent = replace(intent, "
700                         + "';l.profile=" + serial + ";', ';') where itemType = 0;";
701                 db.execSQL(sql);
702             }
703         }
704 
705         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)706         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
707             if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
708             switch (oldVersion) {
709                 // The version cannot be lower that 12, as Launcher3 never supported a lower
710                 // version of the DB.
711                 case 12: {
712                     // With the new shrink-wrapped and re-orderable workspaces, it makes sense
713                     // to persist workspace screens and their relative order.
714                     mMaxScreenId = 0;
715                     addWorkspacesTable(db, false);
716                 }
717                 case 13: {
718                     try (SQLiteTransaction t = new SQLiteTransaction(db)) {
719                         // Insert new column for holding widget provider name
720                         db.execSQL("ALTER TABLE favorites " +
721                                 "ADD COLUMN appWidgetProvider TEXT;");
722                         t.commit();
723                     } catch (SQLException ex) {
724                         Log.e(TAG, ex.getMessage(), ex);
725                         // Old version remains, which means we wipe old data
726                         break;
727                     }
728                 }
729                 case 14: {
730                     try (SQLiteTransaction t = new SQLiteTransaction(db)) {
731                         // Insert new column for holding update timestamp
732                         db.execSQL("ALTER TABLE favorites " +
733                                 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
734                         db.execSQL("ALTER TABLE workspaceScreens " +
735                                 "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
736                         t.commit();
737                     } catch (SQLException ex) {
738                         Log.e(TAG, ex.getMessage(), ex);
739                         // Old version remains, which means we wipe old data
740                         break;
741                     }
742                 }
743                 case 15: {
744                     if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
745                         // Old version remains, which means we wipe old data
746                         break;
747                     }
748                 }
749                 case 16: {
750                     // No-op
751                 }
752                 case 17: {
753                     // No-op
754                 }
755                 case 18: {
756                     // Due to a data loss bug, some users may have items associated with screen ids
757                     // which no longer exist. Since this can cause other problems, and since the user
758                     // will never see these items anyway, we use database upgrade as an opportunity to
759                     // clean things up.
760                     removeOrphanedItems(db);
761                 }
762                 case 19: {
763                     // Add userId column
764                     if (!addProfileColumn(db)) {
765                         // Old version remains, which means we wipe old data
766                         break;
767                     }
768                 }
769                 case 20:
770                     if (!updateFolderItemsRank(db, true)) {
771                         break;
772                     }
773                 case 21:
774                     // Recreate workspace table with screen id a primary key
775                     if (!recreateWorkspaceTable(db)) {
776                         break;
777                     }
778                 case 22: {
779                     if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
780                         // Old version remains, which means we wipe old data
781                         break;
782                     }
783                 }
784                 case 23:
785                     // No-op
786                 case 24:
787                     // No-op
788                 case 25:
789                     convertShortcutsToLauncherActivities(db);
790                 case 26:
791                     // QSB was moved to the grid. Clear the first row on screen 0.
792                     if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
793                             !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
794                         break;
795                     }
796                 case 27:
797                     // DB Upgraded successfully
798                     return;
799             }
800 
801             // DB was not upgraded
802             Log.w(TAG, "Destroying all old data.");
803             createEmptyDB(db);
804         }
805 
806         @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)807         public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
808             try {
809                 DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE))
810                         .onDowngrade(db, oldVersion, newVersion);
811             } catch (Exception e) {
812                 Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion +
813                         ". Wiping databse.", e);
814                 createEmptyDB(db);
815             }
816         }
817 
818         /**
819          * Clears all the data for a fresh start.
820          */
createEmptyDB(SQLiteDatabase db)821         public void createEmptyDB(SQLiteDatabase db) {
822             try (SQLiteTransaction t = new SQLiteTransaction(db)) {
823                 db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
824                 db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
825                 onCreate(db);
826                 t.commit();
827             }
828         }
829 
830         /**
831          * Removes widgets which are registered to the Launcher's host, but are not present
832          * in our model.
833          */
834         @TargetApi(Build.VERSION_CODES.O)
removeGhostWidgets(SQLiteDatabase db)835         public void removeGhostWidgets(SQLiteDatabase db) {
836             // Get all existing widget ids.
837             final AppWidgetHost host = newLauncherWidgetHost();
838             final int[] allWidgets;
839             try {
840                 // Although the method was defined in O, it has existed since the beginning of time,
841                 // so it might work on older platforms as well.
842                 allWidgets = host.getAppWidgetIds();
843             } catch (IncompatibleClassChangeError e) {
844                 Log.e(TAG, "getAppWidgetIds not supported", e);
845                 return;
846             }
847             final HashSet<Integer> validWidgets = new HashSet<>();
848             try (Cursor c = db.query(Favorites.TABLE_NAME,
849                     new String[] {Favorites.APPWIDGET_ID },
850                     "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null)) {
851                 while (c.moveToNext()) {
852                     validWidgets.add(c.getInt(0));
853                 }
854             } catch (SQLException ex) {
855                 Log.w(TAG, "Error getting widgets list", ex);
856                 return;
857             }
858             for (int widgetId : allWidgets) {
859                 if (!validWidgets.contains(widgetId)) {
860                     try {
861                         FileLog.d(TAG, "Deleting invalid widget " + widgetId);
862                         host.deleteAppWidgetId(widgetId);
863                     } catch (RuntimeException e) {
864                         // Ignore
865                     }
866                 }
867             }
868         }
869 
870         /**
871          * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
872          * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
873          */
convertShortcutsToLauncherActivities(SQLiteDatabase db)874         @Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
875             try (SQLiteTransaction t = new SQLiteTransaction(db);
876                  // Only consider the primary user as other users can't have a shortcut.
877                  Cursor c = db.query(Favorites.TABLE_NAME,
878                          new String[] { Favorites._ID, Favorites.INTENT},
879                          "itemType=" + Favorites.ITEM_TYPE_SHORTCUT +
880                                  " AND profileId=" + getDefaultUserSerial(),
881                          null, null, null, null);
882                  SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType="
883                          + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?")
884             ) {
885                 final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
886                 final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
887 
888                 while (c.moveToNext()) {
889                     String intentDescription = c.getString(intentIndex);
890                     Intent intent;
891                     try {
892                         intent = Intent.parseUri(intentDescription, 0);
893                     } catch (URISyntaxException e) {
894                         Log.e(TAG, "Unable to parse intent", e);
895                         continue;
896                     }
897 
898                     if (!Utilities.isLauncherAppTarget(intent)) {
899                         continue;
900                     }
901 
902                     long id = c.getLong(idIndex);
903                     updateStmt.bindLong(1, id);
904                     updateStmt.executeUpdateDelete();
905                 }
906                 t.commit();
907             } catch (SQLException ex) {
908                 Log.w(TAG, "Error deduping shortcuts", ex);
909             }
910         }
911 
912         /**
913          * Recreates workspace table and migrates data to the new table.
914          */
recreateWorkspaceTable(SQLiteDatabase db)915         public boolean recreateWorkspaceTable(SQLiteDatabase db) {
916             try (SQLiteTransaction t = new SQLiteTransaction(db)) {
917                 final ArrayList<Long> sortedIDs;
918 
919                 try (Cursor c = db.query(WorkspaceScreens.TABLE_NAME,
920                         new String[] {LauncherSettings.WorkspaceScreens._ID},
921                         null, null, null, null,
922                         LauncherSettings.WorkspaceScreens.SCREEN_RANK)) {
923                     // Use LinkedHashSet so that ordering is preserved
924                     sortedIDs = new ArrayList<>(
925                             LauncherDbUtils.iterateCursor(c, 0, new LinkedHashSet<Long>()));
926                 }
927                 db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
928                 addWorkspacesTable(db, false);
929 
930                 // Add all screen ids back
931                 int total = sortedIDs.size();
932                 for (int i = 0; i < total; i++) {
933                     ContentValues values = new ContentValues();
934                     values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i));
935                     values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
936                     addModifiedTime(values);
937                     db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values);
938                 }
939                 t.commit();
940                 mMaxScreenId = sortedIDs.isEmpty() ? 0 : Collections.max(sortedIDs);
941             } catch (SQLException ex) {
942                 // Old version remains, which means we wipe old data
943                 Log.e(TAG, ex.getMessage(), ex);
944                 return false;
945             }
946             return true;
947         }
948 
updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn)949         @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
950             try (SQLiteTransaction t = new SQLiteTransaction(db)) {
951                 if (addRankColumn) {
952                     // Insert new column for holding rank
953                     db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
954                 }
955 
956                 // Get a map for folder ID to folder width
957                 Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
958                         + " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
959                         + " GROUP BY container;",
960                         new String[] {Integer.toString(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)});
961 
962                 while (c.moveToNext()) {
963                     db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
964                             + "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
965                             new Object[] {c.getLong(1) + 1, c.getLong(0)});
966                 }
967 
968                 c.close();
969                 t.commit();
970             } catch (SQLException ex) {
971                 // Old version remains, which means we wipe old data
972                 Log.e(TAG, ex.getMessage(), ex);
973                 return false;
974             }
975             return true;
976         }
977 
addProfileColumn(SQLiteDatabase db)978         private boolean addProfileColumn(SQLiteDatabase db) {
979             return addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial());
980         }
981 
addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue)982         private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
983             try (SQLiteTransaction t = new SQLiteTransaction(db)) {
984                 db.execSQL("ALTER TABLE favorites ADD COLUMN "
985                         + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
986                 t.commit();
987             } catch (SQLException ex) {
988                 Log.e(TAG, ex.getMessage(), ex);
989                 return false;
990             }
991             return true;
992         }
993 
994         // Generates a new ID to use for an object in your database. This method should be only
995         // called from the main UI thread. As an exception, we do call it when we call the
996         // constructor from the worker thread; however, this doesn't extend until after the
997         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
998         // after that point
999         @Override
generateNewItemId()1000         public long generateNewItemId() {
1001             if (mMaxItemId < 0) {
1002                 throw new RuntimeException("Error: max item id was not initialized");
1003             }
1004             mMaxItemId += 1;
1005             return mMaxItemId;
1006         }
1007 
newLauncherWidgetHost()1008         public AppWidgetHost newLauncherWidgetHost() {
1009             return new LauncherAppWidgetHost(mContext);
1010         }
1011 
1012         @Override
insertAndCheck(SQLiteDatabase db, ContentValues values)1013         public long insertAndCheck(SQLiteDatabase db, ContentValues values) {
1014             return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
1015         }
1016 
checkId(String table, ContentValues values)1017         public void checkId(String table, ContentValues values) {
1018             long id = values.getAsLong(LauncherSettings.BaseLauncherColumns._ID);
1019             if (WorkspaceScreens.TABLE_NAME.equals(table)) {
1020                 mMaxScreenId = Math.max(id, mMaxScreenId);
1021             }  else {
1022                 mMaxItemId = Math.max(id, mMaxItemId);
1023             }
1024         }
1025 
initializeMaxItemId(SQLiteDatabase db)1026         private long initializeMaxItemId(SQLiteDatabase db) {
1027             return getMaxId(db, Favorites.TABLE_NAME);
1028         }
1029 
1030         // Generates a new ID to use for an workspace screen in your database. This method
1031         // should be only called from the main UI thread. As an exception, we do call it when we
1032         // call the constructor from the worker thread; however, this doesn't extend until after the
1033         // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
1034         // after that point
generateNewScreenId()1035         public long generateNewScreenId() {
1036             if (mMaxScreenId < 0) {
1037                 throw new RuntimeException("Error: max screen id was not initialized");
1038             }
1039             mMaxScreenId += 1;
1040             return mMaxScreenId;
1041         }
1042 
initializeMaxScreenId(SQLiteDatabase db)1043         private long initializeMaxScreenId(SQLiteDatabase db) {
1044             return getMaxId(db, WorkspaceScreens.TABLE_NAME);
1045         }
1046 
loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader)1047         @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
1048             ArrayList<Long> screenIds = new ArrayList<Long>();
1049             // TODO: Use multiple loaders with fall-back and transaction.
1050             int count = loader.loadLayout(db, screenIds);
1051 
1052             // Add the screens specified by the items above
1053             Collections.sort(screenIds);
1054             int rank = 0;
1055             ContentValues values = new ContentValues();
1056             for (Long id : screenIds) {
1057                 values.clear();
1058                 values.put(LauncherSettings.WorkspaceScreens._ID, id);
1059                 values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
1060                 if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) {
1061                     throw new RuntimeException("Failed initialize screen table"
1062                             + "from default layout");
1063                 }
1064                 rank++;
1065             }
1066 
1067             // Ensure that the max ids are initialized
1068             mMaxItemId = initializeMaxItemId(db);
1069             mMaxScreenId = initializeMaxScreenId(db);
1070 
1071             return count;
1072         }
1073     }
1074 
1075     /**
1076      * @return the max _id in the provided table.
1077      */
getMaxId(SQLiteDatabase db, String table)1078     @Thunk static long getMaxId(SQLiteDatabase db, String table) {
1079         Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
1080         // get the result
1081         long id = -1;
1082         if (c != null && c.moveToNext()) {
1083             id = c.getLong(0);
1084         }
1085         if (c != null) {
1086             c.close();
1087         }
1088 
1089         if (id == -1) {
1090             throw new RuntimeException("Error: could not query max id in " + table);
1091         }
1092 
1093         return id;
1094     }
1095 
1096     static class SqlArguments {
1097         public final String table;
1098         public final String where;
1099         public final String[] args;
1100 
SqlArguments(Uri url, String where, String[] args)1101         SqlArguments(Uri url, String where, String[] args) {
1102             if (url.getPathSegments().size() == 1) {
1103                 this.table = url.getPathSegments().get(0);
1104                 this.where = where;
1105                 this.args = args;
1106             } else if (url.getPathSegments().size() != 2) {
1107                 throw new IllegalArgumentException("Invalid URI: " + url);
1108             } else if (!TextUtils.isEmpty(where)) {
1109                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
1110             } else {
1111                 this.table = url.getPathSegments().get(0);
1112                 this.where = "_id=" + ContentUris.parseId(url);
1113                 this.args = null;
1114             }
1115         }
1116 
SqlArguments(Uri url)1117         SqlArguments(Uri url) {
1118             if (url.getPathSegments().size() == 1) {
1119                 table = url.getPathSegments().get(0);
1120                 where = null;
1121                 args = null;
1122             } else {
1123                 throw new IllegalArgumentException("Invalid URI: " + url);
1124             }
1125         }
1126     }
1127 
1128     private static class ChangeListenerWrapper implements Handler.Callback {
1129 
1130         private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1;
1131         private static final int MSG_APP_WIDGET_HOST_RESET = 2;
1132 
1133         private LauncherProviderChangeListener mListener;
1134 
1135         @Override
handleMessage(Message msg)1136         public boolean handleMessage(Message msg) {
1137             if (mListener != null) {
1138                 switch (msg.what) {
1139                     case MSG_LAUNCHER_PROVIDER_CHANGED:
1140                         mListener.onLauncherProviderChanged();
1141                         break;
1142                     case MSG_APP_WIDGET_HOST_RESET:
1143                         mListener.onAppWidgetHostReset();
1144                         break;
1145                 }
1146             }
1147             return true;
1148         }
1149     }
1150 }
1151