Simplifying Model data load state management

Instead of maintaining 3 different states, each tied to a subset of data,
maintaing a single state that represents all the data. Individual subset
data is invalidated in rare cases and these invalidates are tightly tied
to the UI. This also allows us to add new data to the model, without worring
about classifying the data into a subset.

Bug: 34112546
Change-Id: Id9cb273de35b79e84a2ef8d6556fcf1e72fb4b75
This commit is contained in:
Sunny Goyal 2017-02-17 11:22:34 -08:00
parent 9f0fa84439
commit dd96a5e4fd
5 changed files with 78 additions and 122 deletions

View File

@ -83,7 +83,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
LauncherAppState app = LauncherAppState.getInstanceNoCreate(); LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) { if (app != null) {
app.reloadWorkspace(); app.getModel().forceReload();
} }
asyncResult.finish(); asyncResult.finish();
} }

View File

@ -134,15 +134,6 @@ public class LauncherAppState {
PackageInstallerCompat.getInstance(mContext).onStop(); PackageInstallerCompat.getInstance(mContext).onStop();
} }
/**
* Reloads the workspace items from the DB and re-binds the workspace. This should generally
* not be called as DB updates are automatically followed by UI update
*/
public void reloadWorkspace() {
mModel.resetLoadedState(false, true);
mModel.startLoaderFromBackground();
}
LauncherModel setLauncher(Launcher launcher) { LauncherModel setLauncher(Launcher launcher) {
getLocalProvider(mContext).setLauncherProviderChangeListener(launcher); getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher); mModel.initialize(launcher);

View File

@ -71,8 +71,6 @@ import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.ManagedProfileHeuristic;
import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageManagerHelper;
@ -93,6 +91,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
@ -123,12 +122,11 @@ public class LauncherModel extends BroadcastReceiver
} }
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper()); @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
// We start off with everything not loaded. After that, we assume that // Indicates whether the current model data is valid or not.
// We start off with everything not loaded. After that, we assume that
// our monitoring of the package manager provides all updates and we never // our monitoring of the package manager provides all updates and we never
// need to do a requery. These are only ever touched from the loader thread. // need to do a requery. This is only ever touched from the loader thread.
private boolean mWorkspaceLoaded; private boolean mModelLoaded;
private boolean mAllAppsLoaded;
private boolean mDeepShortcutsLoaded;
/** /**
* Set of runnables to be called on the background thread after the workspace binding * Set of runnables to be called on the background thread after the workspace binding
@ -148,11 +146,11 @@ public class LauncherModel extends BroadcastReceiver
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
if (mDeepShortcutsLoaded) { if (mModelLoaded) {
boolean hasShortcutHostPermission = boolean hasShortcutHostPermission =
DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission(); DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
if (hasShortcutHostPermission != mHasShortcutHostPermission) { if (hasShortcutHostPermission != mHasShortcutHostPermission) {
mApp.reloadWorkspace(); forceReload();
} }
} }
} }
@ -480,8 +478,16 @@ public class LauncherModel extends BroadcastReceiver
} }
} }
void forceReload() { /**
resetLoadedState(true, true); * Reloads the workspace items from the DB and re-binds the workspace. This should generally
* not be called as DB updates are automatically followed by UI update
*/
public void forceReload() {
synchronized (mLock) {
// Stop any existing loaders first, so they don't set mModelLoaded to true later
stopLoaderLocked();
mModelLoaded = false;
}
// Do this here because if the launcher activity is running it will be restarted. // Do this here because if the launcher activity is running it will be restarted.
// If it's not running startLoaderFromBackground will merely tell it that it needs // If it's not running startLoaderFromBackground will merely tell it that it needs
@ -489,19 +495,6 @@ public class LauncherModel extends BroadcastReceiver
startLoaderFromBackground(); startLoaderFromBackground();
} }
public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
synchronized (mLock) {
// Stop any existing loaders first, so they don't set mAllAppsLoaded or
// mWorkspaceLoaded to true later
stopLoaderLocked();
if (resetAllAppsLoaded) mAllAppsLoaded = false;
if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
// Always reset deep shortcuts loaded.
// TODO: why?
mDeepShortcutsLoaded = false;
}
}
/** /**
* When the launcher is in the background, it's possible for it to miss paired * When the launcher is in the background, it's possible for it to miss paired
* configuration changes. So whenever we trigger the loader from the background * configuration changes. So whenever we trigger the loader from the background
@ -553,9 +546,8 @@ public class LauncherModel extends BroadcastReceiver
// If there is already one running, tell it to stop. // If there is already one running, tell it to stop.
stopLoaderLocked(); stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage); mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
// TODO: mDeepShortcutsLoaded does not need to be true for synchronous bind. if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded && mModelLoaded && !mIsLoaderTaskRunning) {
&& mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage); mLoaderTask.runBindSynchronousPage(synchronousBindPage);
return true; return true;
} else { } else {
@ -607,28 +599,6 @@ public class LauncherModel extends BroadcastReceiver
mPageToBindFirst = pageToBindFirst; mPageToBindFirst = pageToBindFirst;
} }
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
// Load the workspace
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
if (!mWorkspaceLoaded) {
loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(mPageToBindFirst);
}
private void waitForIdle() { private void waitForIdle() {
// Wait until the either we're stopped or the other threads are done. // Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled // This way we don't start loading all apps until the workspace has settled
@ -671,7 +641,7 @@ public class LauncherModel extends BroadcastReceiver
throw new RuntimeException("Should not call runBindSynchronousPage() without " + throw new RuntimeException("Should not call runBindSynchronousPage() without " +
"valid page index"); "valid page index");
} }
if (!mAllAppsLoaded || !mWorkspaceLoaded) { if (!mModelLoaded) {
// Ensure that we don't try and bind a specified page when the pages have not been // Ensure that we don't try and bind a specified page when the pages have not been
// loaded already (we should load everything asynchronously in that case) // loaded already (we should load everything asynchronously in that case)
throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
@ -703,6 +673,14 @@ public class LauncherModel extends BroadcastReceiver
bindDeepShortcuts(); bindDeepShortcuts();
} }
private void verifyNotStopped() throws CancellationException {
synchronized (LoaderTask.this) {
if (mStopped) {
throw new CancellationException("Loader stopped");
}
}
}
public void run() { public void run() {
synchronized (mLock) { synchronized (mLock) {
if (mStopped) { if (mStopped) {
@ -710,41 +688,62 @@ public class LauncherModel extends BroadcastReceiver
} }
mIsLoaderTaskRunning = true; mIsLoaderTaskRunning = true;
} }
// Optimize for end-user experience: if the Launcher is up and // running with the
// All Apps interface in the foreground, load All Apps first. Otherwise, load the
// workspace first (default).
keep_running: {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
if (mStopped) { try {
break keep_running; if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
} // Set to false in bindWorkspace()
mIsLoadingAndBindingWorkspace = true;
loadWorkspace();
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
bindWorkspace(mPageToBindFirst);
// Take a break
if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle");
waitForIdle(); waitForIdle();
verifyNotStopped();
// second step // second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
loadAndBindAllApps(); loadAllApps();
verifyNotStopped();
if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
updateIconCache();
// Take a break
if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle");
waitForIdle(); waitForIdle();
verifyNotStopped();
// third step // third step
if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts"); if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
loadAndBindDeepShortcuts(); loadDeepShortcuts();
}
// Clear out this reference, otherwise we end up holding it until all of the verifyNotStopped();
// callback runnables are done. if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
mContext = null; bindDeepShortcuts();
synchronized (mLock) { synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves. // Everything loaded bind the data.
if (mLoaderTask == this) { mModelLoaded = true;
mLoaderTask = null; mHasLoaderCompletedOnce = true;
}
} catch (CancellationException e) {
// Loader stopped, ignore
} finally {
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
if (mLoaderTask == this) {
mLoaderTask = null;
}
mIsLoaderTaskRunning = false;
} }
mIsLoaderTaskRunning = false;
mHasLoaderCompletedOnce = true;
} }
} }
@ -1605,29 +1604,6 @@ public class LauncherModel extends BroadcastReceiver
} }
} }
private void loadAndBindAllApps() {
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
}
if (!mAllAppsLoaded) {
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
}
updateIconCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
private void updateIconCache() { private void updateIconCache() {
// Ignore packages which have a promise icon. // Ignore packages which have a promise icon.
HashSet<String> packagesToIgnore = new HashSet<>(); HashSet<String> packagesToIgnore = new HashSet<>();
@ -1768,11 +1744,8 @@ public class LauncherModel extends BroadcastReceiver
} }
} }
private void loadAndBindDeepShortcuts() { private void loadDeepShortcuts() {
if (DEBUG_LOADERS) { if (!mModelLoaded) {
Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
}
if (!mDeepShortcutsLoaded) {
sBgDataModel.deepShortcutMap.clear(); sBgDataModel.deepShortcutMap.clear();
DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext); DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
mHasShortcutHostPermission = shortcutManager.hasHostPermission(); mHasShortcutHostPermission = shortcutManager.hasHostPermission();
@ -1785,14 +1758,7 @@ public class LauncherModel extends BroadcastReceiver
} }
} }
} }
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mDeepShortcutsLoaded = true;
}
} }
bindDeepShortcuts();
} }
} }

View File

@ -174,7 +174,7 @@ public class LauncherProvider extends ContentProvider {
if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) { if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
LauncherAppState app = LauncherAppState.getInstanceNoCreate(); LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) { if (app != null) {
app.reloadWorkspace(); app.getModel().forceReload();
} }
} }
} }
@ -205,7 +205,7 @@ public class LauncherProvider extends ContentProvider {
// Deprecated behavior to support legacy devices which rely on provider callbacks. // Deprecated behavior to support legacy devices which rely on provider callbacks.
LauncherAppState app = LauncherAppState.getInstanceNoCreate(); LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) { if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
app.reloadWorkspace(); app.getModel().forceReload();
} }
String notify = uri.getQueryParameter("notify"); String notify = uri.getQueryParameter("notify");

View File

@ -236,8 +236,7 @@ public class LauncherInstrumentationTestCase extends InstrumentationTestCase {
@Override @Override
public void run() { public void run() {
ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
LauncherAppState.getInstance(mTargetContext).getModel() LauncherAppState.getInstance(mTargetContext).getModel().forceReload();
.resetLoadedState(true, true);
} }
}); });
} catch (Throwable t) { } catch (Throwable t) {