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 static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
20 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
22 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
23 
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ShortcutInfo;
28 import android.os.Process;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.Pair;
33 
34 import androidx.annotation.Nullable;
35 
36 import com.android.launcher3.compat.LauncherAppsCompat;
37 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
38 import com.android.launcher3.compat.UserManagerCompat;
39 import com.android.launcher3.icons.IconCache;
40 import com.android.launcher3.icons.LauncherIcons;
41 import com.android.launcher3.logging.FileLog;
42 import com.android.launcher3.model.AddWorkspaceItemsTask;
43 import com.android.launcher3.model.AllAppsList;
44 import com.android.launcher3.model.BaseModelUpdateTask;
45 import com.android.launcher3.model.BgDataModel;
46 import com.android.launcher3.model.BgDataModel.Callbacks;
47 import com.android.launcher3.model.CacheDataUpdatedTask;
48 import com.android.launcher3.model.LoaderResults;
49 import com.android.launcher3.model.LoaderTask;
50 import com.android.launcher3.model.ModelWriter;
51 import com.android.launcher3.model.PackageInstallStateChangedTask;
52 import com.android.launcher3.model.PackageUpdatedTask;
53 import com.android.launcher3.model.ShortcutsChangedTask;
54 import com.android.launcher3.model.UserLockStateChangedTask;
55 import com.android.launcher3.shortcuts.DeepShortcutManager;
56 import com.android.launcher3.util.IntSparseArrayMap;
57 import com.android.launcher3.util.ItemInfoMatcher;
58 import com.android.launcher3.util.PackageUserKey;
59 import com.android.launcher3.util.Preconditions;
60 import com.android.launcher3.util.Thunk;
61 
62 import java.io.FileDescriptor;
63 import java.io.PrintWriter;
64 import java.lang.ref.WeakReference;
65 import java.util.ArrayList;
66 import java.util.HashSet;
67 import java.util.List;
68 import java.util.concurrent.CancellationException;
69 import java.util.concurrent.Executor;
70 import java.util.function.Supplier;
71 
72 /**
73  * Maintains in-memory state of the Launcher. It is expected that there should be only one
74  * LauncherModel object held in a static. Also provide APIs for updating the database state
75  * for the Launcher.
76  */
77 public class LauncherModel extends BroadcastReceiver
78         implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
79     private static final boolean DEBUG_RECEIVER = false;
80 
81     static final String TAG = "Launcher.Model";
82 
83     @Thunk final LauncherAppState mApp;
84     @Thunk final Object mLock = new Object();
85     @Thunk
86     LoaderTask mLoaderTask;
87     @Thunk boolean mIsLoaderTaskRunning;
88 
89     // Indicates whether the current model data is valid or not.
90     // We start off with everything not loaded. After that, we assume that
91     // our monitoring of the package manager provides all updates and we never
92     // need to do a requery. This is only ever touched from the loader thread.
93     private boolean mModelLoaded;
isModelLoaded()94     public boolean isModelLoaded() {
95         synchronized (mLock) {
96             return mModelLoaded && mLoaderTask == null;
97         }
98     }
99 
100     @Thunk WeakReference<Callbacks> mCallbacks;
101 
102     // < only access in worker thread >
103     private final AllAppsList mBgAllAppsList;
104 
105     /**
106      * All the static data should be accessed on the background thread, A lock should be acquired
107      * on this object when accessing any data from this model.
108      */
109     static final BgDataModel sBgDataModel = new BgDataModel();
110 
111     // Runnable to check if the shortcuts permission has changed.
112     private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
113         @Override
114         public void run() {
115             if (mModelLoaded) {
116                 boolean hasShortcutHostPermission =
117                         DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
118                 if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) {
119                     forceReload();
120                 }
121             }
122         }
123     };
124 
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter)125     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
126         mApp = app;
127         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
128     }
129 
setPackageState(PackageInstallInfo installInfo)130     public void setPackageState(PackageInstallInfo installInfo) {
131         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
132     }
133 
134     /**
135      * Updates the icons and label of all pending icons for the provided package name.
136      */
updateSessionDisplayInfo(final String packageName, final UserHandle user)137     public void updateSessionDisplayInfo(final String packageName, final UserHandle user) {
138         HashSet<String> packages = new HashSet<>();
139         packages.add(packageName);
140         enqueueModelUpdateTask(new CacheDataUpdatedTask(
141                 CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages));
142     }
143 
144     /**
145      * Adds the provided items to the workspace.
146      */
addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList)147     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
148         Callbacks callbacks = getCallback();
149         if (callbacks != null) {
150             callbacks.preAddApps();
151         }
152         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
153     }
154 
getWriter(boolean hasVerticalHotseat, boolean verifyChanges)155     public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
156         return new ModelWriter(mApp.getContext(), this, sBgDataModel,
157                 hasVerticalHotseat, verifyChanges);
158     }
159 
160     /**
161      * Set this as the current Launcher activity object for the loader.
162      */
initialize(Callbacks callbacks)163     public void initialize(Callbacks callbacks) {
164         synchronized (mLock) {
165             Preconditions.assertUIThread();
166             mCallbacks = new WeakReference<>(callbacks);
167         }
168     }
169 
170     @Override
onPackageChanged(String packageName, UserHandle user)171     public void onPackageChanged(String packageName, UserHandle user) {
172         int op = PackageUpdatedTask.OP_UPDATE;
173         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
174     }
175 
onSessionFailure(String packageName, UserHandle user)176     public void onSessionFailure(String packageName, UserHandle user) {
177         enqueueModelUpdateTask(new BaseModelUpdateTask() {
178             @Override
179             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
180                 final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
181                 synchronized (dataModel) {
182                     for (ItemInfo info : dataModel.itemsIdMap) {
183                         if (info instanceof WorkspaceItemInfo
184                                 && ((WorkspaceItemInfo) info).hasPromiseIconUi()
185                                 && user.equals(info.user)
186                                 && info.getIntent() != null
187                                 && TextUtils.equals(packageName, info.getIntent().getPackage())) {
188                             removedIds.put(info.id, true /* remove */);
189                         }
190                     }
191                 }
192 
193                 if (!removedIds.isEmpty()) {
194                     deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
195                 }
196             }
197         });
198     }
199 
200     @Override
onPackageRemoved(String packageName, UserHandle user)201     public void onPackageRemoved(String packageName, UserHandle user) {
202         onPackagesRemoved(user, packageName);
203     }
204 
onPackagesRemoved(UserHandle user, String... packages)205     public void onPackagesRemoved(UserHandle user, String... packages) {
206         int op = PackageUpdatedTask.OP_REMOVE;
207         FileLog.d(TAG, "package removed received " + String.join("," + packages));
208         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
209     }
210 
211     @Override
onPackageAdded(String packageName, UserHandle user)212     public void onPackageAdded(String packageName, UserHandle user) {
213         int op = PackageUpdatedTask.OP_ADD;
214         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
215     }
216 
217     @Override
onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)218     public void onPackagesAvailable(String[] packageNames, UserHandle user,
219             boolean replacing) {
220         enqueueModelUpdateTask(
221                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
222     }
223 
224     @Override
onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)225     public void onPackagesUnavailable(String[] packageNames, UserHandle user,
226             boolean replacing) {
227         if (!replacing) {
228             enqueueModelUpdateTask(new PackageUpdatedTask(
229                     PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
230         }
231     }
232 
233     @Override
onPackagesSuspended(String[] packageNames, UserHandle user)234     public void onPackagesSuspended(String[] packageNames, UserHandle user) {
235         enqueueModelUpdateTask(new PackageUpdatedTask(
236                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
237     }
238 
239     @Override
onPackagesUnsuspended(String[] packageNames, UserHandle user)240     public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
241         enqueueModelUpdateTask(new PackageUpdatedTask(
242                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
243     }
244 
245     @Override
onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts, UserHandle user)246     public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
247             UserHandle user) {
248         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
249     }
250 
updatePinnedShortcuts(String packageName, List<ShortcutInfo> shortcuts, UserHandle user)251     public void updatePinnedShortcuts(String packageName, List<ShortcutInfo> shortcuts,
252             UserHandle user) {
253         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
254     }
255 
256     /**
257      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
258      * ACTION_PACKAGE_CHANGED.
259      */
260     @Override
onReceive(Context context, Intent intent)261     public void onReceive(Context context, Intent intent) {
262         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
263         final String action = intent.getAction();
264         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
265             // If we have changed locale we need to clear out the labels in all apps/workspace.
266             forceReload();
267         } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
268                 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
269             UserManagerCompat.getInstance(context).enableAndResetCache();
270             forceReload();
271         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
272                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
273                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
274             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
275             if (user != null) {
276                 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
277                         Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
278                     enqueueModelUpdateTask(new PackageUpdatedTask(
279                             PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
280                 }
281 
282                 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
283                 // we need to run the state change task again.
284                 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
285                         Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
286                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
287                 }
288             }
289         } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
290             Launcher l = (Launcher) getCallback();
291             l.reload();
292         }
293     }
294 
forceReload()295     public void forceReload() {
296         forceReload(-1);
297     }
298 
299     /**
300      * Reloads the workspace items from the DB and re-binds the workspace. This should generally
301      * not be called as DB updates are automatically followed by UI update
302      * @param synchronousBindPage The page to bind first. Can pass -1 to use the current page.
303      */
forceReload(int synchronousBindPage)304     public void forceReload(int synchronousBindPage) {
305         synchronized (mLock) {
306             // Stop any existing loaders first, so they don't set mModelLoaded to true later
307             stopLoader();
308             mModelLoaded = false;
309         }
310 
311         // Start the loader if launcher is already running, otherwise the loader will run,
312         // the next time launcher starts
313         Callbacks callbacks = getCallback();
314         if (callbacks != null) {
315             if (synchronousBindPage < 0) {
316                 synchronousBindPage = callbacks.getCurrentWorkspaceScreen();
317             }
318             startLoader(synchronousBindPage);
319         }
320     }
321 
isCurrentCallbacks(Callbacks callbacks)322     public boolean isCurrentCallbacks(Callbacks callbacks) {
323         return (mCallbacks != null && mCallbacks.get() == callbacks);
324     }
325 
326     /**
327      * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
328      * @return true if the page could be bound synchronously.
329      */
startLoader(int synchronousBindPage)330     public boolean startLoader(int synchronousBindPage) {
331         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
332         InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
333         synchronized (mLock) {
334             // Don't bother to start the thread if we know it's not going to do anything
335             if (mCallbacks != null && mCallbacks.get() != null) {
336                 final Callbacks oldCallbacks = mCallbacks.get();
337                 // Clear any pending bind-runnables from the synchronized load process.
338                 MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
339 
340                 // If there is already one running, tell it to stop.
341                 stopLoader();
342                 LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
343                         mBgAllAppsList, synchronousBindPage, mCallbacks);
344                 if (mModelLoaded && !mIsLoaderTaskRunning) {
345                     // Divide the set of loaded items into those that we are binding synchronously,
346                     // and everything else that is to be bound normally (asynchronously).
347                     loaderResults.bindWorkspace();
348                     // For now, continue posting the binding of AllApps as there are other
349                     // issues that arise from that.
350                     loaderResults.bindAllApps();
351                     loaderResults.bindDeepShortcuts();
352                     loaderResults.bindWidgets();
353                     return true;
354                 } else {
355                     startLoaderForResults(loaderResults);
356                 }
357             }
358         }
359         return false;
360     }
361 
362     /**
363      * If there is already a loader task running, tell it to stop.
364      */
stopLoader()365     public void stopLoader() {
366         synchronized (mLock) {
367             LoaderTask oldTask = mLoaderTask;
368             mLoaderTask = null;
369             if (oldTask != null) {
370                 oldTask.stopLocked();
371             }
372         }
373     }
374 
startLoaderForResults(LoaderResults results)375     public void startLoaderForResults(LoaderResults results) {
376         synchronized (mLock) {
377             stopLoader();
378             mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
379 
380             // Always post the loader task, instead of running directly (even on same thread) so
381             // that we exit any nested synchronized blocks
382             MODEL_EXECUTOR.post(mLoaderTask);
383         }
384     }
385 
startLoaderForResultsIfNotLoaded(LoaderResults results)386     public void startLoaderForResultsIfNotLoaded(LoaderResults results) {
387         synchronized (mLock) {
388             if (!isModelLoaded()) {
389                 Log.d(TAG, "Workspace not loaded, loading now");
390                 startLoaderForResults(results);
391             }
392         }
393     }
394 
onInstallSessionCreated(final PackageInstallInfo sessionInfo)395     public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
396         enqueueModelUpdateTask(new BaseModelUpdateTask() {
397             @Override
398             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
399                 apps.addPromiseApp(app.getContext(), sessionInfo);
400                 bindApplicationsIfNeeded();
401             }
402         });
403     }
404 
405     public class LoaderTransaction implements AutoCloseable {
406 
407         private final LoaderTask mTask;
408 
LoaderTransaction(LoaderTask task)409         private LoaderTransaction(LoaderTask task) throws CancellationException {
410             synchronized (mLock) {
411                 if (mLoaderTask != task) {
412                     throw new CancellationException("Loader already stopped");
413                 }
414                 mTask = task;
415                 mIsLoaderTaskRunning = true;
416                 mModelLoaded = false;
417             }
418         }
419 
commit()420         public void commit() {
421             synchronized (mLock) {
422                 // Everything loaded bind the data.
423                 mModelLoaded = true;
424             }
425         }
426 
427         @Override
close()428         public void close() {
429             synchronized (mLock) {
430                 // If we are still the last one to be scheduled, remove ourselves.
431                 if (mLoaderTask == mTask) {
432                     mLoaderTask = null;
433                 }
434                 mIsLoaderTaskRunning = false;
435             }
436         }
437     }
438 
beginLoader(LoaderTask task)439     public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException {
440         return new LoaderTransaction(task);
441     }
442 
443     /**
444      * Refreshes the cached shortcuts if the shortcut permission has changed.
445      * Current implementation simply reloads the workspace, but it can be optimized to
446      * use partial updates similar to {@link UserManagerCompat}
447      */
refreshShortcutsIfRequired()448     public void refreshShortcutsIfRequired() {
449         MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
450         MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
451     }
452 
453     /**
454      * Called when the icons for packages have been updated in the icon cache.
455      */
onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user)456     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
457         // If any package icon has changed (app was updated while launcher was dead),
458         // update the corresponding shortcuts.
459         enqueueModelUpdateTask(new CacheDataUpdatedTask(
460                 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
461     }
462 
463     /**
464      * Called when the labels for the widgets has updated in the icon cache.
465      */
onWidgetLabelsUpdated(HashSet<String> updatedPackages, UserHandle user)466     public void onWidgetLabelsUpdated(HashSet<String> updatedPackages, UserHandle user) {
467         enqueueModelUpdateTask(new BaseModelUpdateTask() {
468             @Override
469             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
470                 dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
471                 bindUpdatedWidgets(dataModel);
472             }
473         });
474     }
475 
enqueueModelUpdateTask(ModelUpdateTask task)476     public void enqueueModelUpdateTask(ModelUpdateTask task) {
477         task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
478         MODEL_EXECUTOR.execute(task);
479     }
480 
481     /**
482      * A task to be executed on the current callbacks on the UI thread.
483      * If there is no current callbacks, the task is ignored.
484      */
485     public interface CallbackTask {
486 
execute(Callbacks callbacks)487         void execute(Callbacks callbacks);
488     }
489 
490     /**
491      * A runnable which changes/updates the data model of the launcher based on certain events.
492      */
493     public interface ModelUpdateTask extends Runnable {
494 
495         /**
496          * Called before the task is posted to initialize the internal state.
497          */
init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor)498         void init(LauncherAppState app, LauncherModel model,
499                 BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
500 
501     }
502 
updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info)503     public void updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info) {
504         updateAndBindWorkspaceItem(() -> {
505             si.updateFromDeepShortcutInfo(info, mApp.getContext());
506             LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
507             si.applyFrom(li.createShortcutIcon(info));
508             li.recycle();
509             return si;
510         });
511     }
512 
513     /**
514      * Utility method to update a shortcut on the background thread.
515      */
updateAndBindWorkspaceItem(final Supplier<WorkspaceItemInfo> itemProvider)516     public void updateAndBindWorkspaceItem(final Supplier<WorkspaceItemInfo> itemProvider) {
517         enqueueModelUpdateTask(new BaseModelUpdateTask() {
518             @Override
519             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
520                 WorkspaceItemInfo info = itemProvider.get();
521                 getModelWriter().updateItemInDatabase(info);
522                 ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
523                 update.add(info);
524                 bindUpdatedWorkspaceItems(update);
525             }
526         });
527     }
528 
refreshAndBindWidgetsAndShortcuts(@ullable final PackageUserKey packageUser)529     public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
530         enqueueModelUpdateTask(new BaseModelUpdateTask() {
531             @Override
532             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
533                 dataModel.widgetsModel.update(app, packageUser);
534                 bindUpdatedWidgets(dataModel);
535             }
536         });
537     }
538 
dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)539     public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
540         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
541             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
542             for (AppInfo info : mBgAllAppsList.data) {
543                 writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
544                         + " componentName=" + info.componentName.getPackageName());
545             }
546         }
547         sBgDataModel.dump(prefix, fd, writer, args);
548     }
549 
getCallback()550     public Callbacks getCallback() {
551         return mCallbacks != null ? mCallbacks.get() : null;
552     }
553 }
554