Adding support for multiple Model clients
Bug: 137568159 Change-Id: Ia4db800b19cc80c695fcb9ea28e07709dfd08c6a
This commit is contained in:
parent
4823b0c99b
commit
a7a5bf3101
|
@ -16,10 +16,11 @@
|
|||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
|
||||
/**
|
||||
* Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
|
||||
|
@ -27,8 +28,13 @@ import java.lang.ref.WeakReference;
|
|||
public class LoaderResults extends BaseLoaderResults {
|
||||
|
||||
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
|
||||
super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
|
||||
AllAppsList allAppsList, Callbacks[] callbacks) {
|
||||
this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
|
||||
}
|
||||
|
||||
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
|
||||
super(app, dataModel, allAppsList, callbacks, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,7 +77,7 @@ public class HotseatEduController {
|
|||
}
|
||||
|
||||
void finishOnboarding() {
|
||||
mLauncher.rebindModel();
|
||||
mLauncher.getModel().rebindCallbacks();
|
||||
mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
|
||||
removeNotification();
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ public class AddWorkspaceItemsTaskTest {
|
|||
@Test
|
||||
public void testAddItem_some_items_added() throws Exception {
|
||||
Callbacks callbacks = mock(Callbacks.class);
|
||||
mModelHelper.getModel().initialize(callbacks);
|
||||
mModelHelper.getModel().addCallbacks(callbacks);
|
||||
|
||||
WorkspaceItemInfo info = new WorkspaceItemInfo();
|
||||
info.intent = new Intent().setComponent(mComponent1);
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
import static org.robolectric.util.ReflectionHelpers.setField;
|
||||
|
||||
|
@ -26,14 +27,10 @@ import android.content.Context;
|
|||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.launcher3.FolderInfo;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherProvider;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
|
@ -48,12 +45,7 @@ import org.junit.runner.RunWith;
|
|||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
|
@ -63,40 +55,22 @@ import java.util.ArrayList;
|
|||
@LooperMode(Mode.PAUSED)
|
||||
public class DefaultLayoutProviderTest {
|
||||
|
||||
private static final String SETTINGS_APP = "com.android.settings";
|
||||
private static final String TEST_PROVIDER_AUTHORITY =
|
||||
DefaultLayoutProviderTest.class.getName().toLowerCase();
|
||||
|
||||
private static final int BITMAP_SIZE = 10;
|
||||
private static final int GRID_SIZE = 4;
|
||||
|
||||
private LauncherModelHelper mModelHelper;
|
||||
private Context mTargetContext;
|
||||
private InvariantDeviceProfile mIdp;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
mTargetContext = RuntimeEnvironment.application;
|
||||
|
||||
mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
|
||||
mIdp.numRows = mIdp.numColumns = mIdp.numHotseatIcons = GRID_SIZE;
|
||||
mIdp.iconBitmapSize = BITMAP_SIZE;
|
||||
|
||||
mModelHelper.provider.setAllowLoadDefaultFavorites(true);
|
||||
Settings.Secure.putString(mTargetContext.getContentResolver(),
|
||||
"launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
|
||||
|
||||
ShadowPackageManager spm = shadowOf(mTargetContext.getPackageManager());
|
||||
spm.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
|
||||
TEST_PROVIDER_AUTHORITY;
|
||||
spm.addActivityIfNotPresent(new ComponentName(SETTINGS_APP, SETTINGS_APP));
|
||||
shadowOf(mTargetContext.getPackageManager())
|
||||
.addActivityIfNotPresent(new ComponentName(TEST_PACKAGE, TEST_PACKAGE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
|
||||
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
|
||||
.putApp(SETTINGS_APP, SETTINGS_APP));
|
||||
.putApp(TEST_PACKAGE, TEST_PACKAGE));
|
||||
|
||||
// Verify one item in hotseat
|
||||
assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
|
||||
|
@ -108,9 +82,9 @@ public class DefaultLayoutProviderTest {
|
|||
@Test
|
||||
public void testCustomProfileLoaded_with_folder() throws Exception {
|
||||
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
|
||||
.addApp(SETTINGS_APP, SETTINGS_APP)
|
||||
.addApp(SETTINGS_APP, SETTINGS_APP)
|
||||
.addApp(SETTINGS_APP, SETTINGS_APP)
|
||||
.addApp(TEST_PACKAGE, TEST_PACKAGE)
|
||||
.addApp(TEST_PACKAGE, TEST_PACKAGE)
|
||||
.addApp(TEST_PACKAGE, TEST_PACKAGE)
|
||||
.build());
|
||||
|
||||
// Verify folder
|
||||
|
@ -146,19 +120,13 @@ public class DefaultLayoutProviderTest {
|
|||
}
|
||||
|
||||
private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
builder.build(new OutputStreamWriter(bos));
|
||||
|
||||
Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, mTargetContext);
|
||||
shadowOf(mTargetContext.getContentResolver()).registerInputStream(layoutUri,
|
||||
new ByteArrayInputStream(bos.toByteArray()));
|
||||
mModelHelper.setupDefaultLayoutProvider(builder);
|
||||
|
||||
LoaderResults results = new LoaderResults(
|
||||
LauncherAppState.getInstance(mTargetContext),
|
||||
mModelHelper.getBgDataModel(),
|
||||
mModelHelper.getAllAppsList(),
|
||||
0,
|
||||
new WeakReference<>(mock(Callbacks.class)));
|
||||
new Callbacks[0]);
|
||||
LoaderTask task = new LoaderTask(
|
||||
LauncherAppState.getInstance(mTargetContext),
|
||||
mModelHelper.getAllAppsList(),
|
||||
|
|
|
@ -190,7 +190,7 @@ public class GridSizeMigrationTaskTest {
|
|||
|
||||
@Test
|
||||
public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
|
||||
// First screen has 2 items that need to be moved, but second screen has only one
|
||||
// First screen has 2 mItems that need to be moved, but second screen has only one
|
||||
// empty space after migration (top-left corner)
|
||||
int[][][] ids = mModelHelper.createGrid(new int[][][]{{
|
||||
{ 0, 0, 0, 1},
|
||||
|
@ -277,7 +277,7 @@ public class GridSizeMigrationTaskTest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Verifies that the workspace items are arranged in the provided order.
|
||||
* Verifies that the workspace mItems are arranged in the provided order.
|
||||
* @param ids A 3d array where the first dimension represents the screen, and the rest two
|
||||
* represent the workspace grid.
|
||||
*/
|
||||
|
|
|
@ -46,7 +46,6 @@ import static org.robolectric.Shadows.shadowOf;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.Process;
|
||||
|
||||
|
@ -77,7 +76,6 @@ public class LoaderCursorTest {
|
|||
private MatrixCursor mCursor;
|
||||
private InvariantDeviceProfile mIDP;
|
||||
private Context mContext;
|
||||
private LauncherApps mLauncherApps;
|
||||
|
||||
private LoaderCursor mLoaderCursor;
|
||||
|
||||
|
@ -86,7 +84,6 @@ public class LoaderCursorTest {
|
|||
mContext = RuntimeEnvironment.application;
|
||||
mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
|
||||
mApp = LauncherAppState.getInstance(mContext);
|
||||
mLauncherApps = mContext.getSystemService(LauncherApps.class);
|
||||
|
||||
mCursor = new MatrixCursor(new String[] {
|
||||
ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
|
||||
|
@ -174,7 +171,7 @@ public class LoaderCursorTest {
|
|||
mIDP.numColumns = 4;
|
||||
mIDP.numHotseatIcons = 3;
|
||||
|
||||
// Overlapping items are not placed
|
||||
// Overlapping mItems are not placed
|
||||
assertTrue(mLoaderCursor.checkItemPlacement(
|
||||
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
|
||||
assertFalse(mLoaderCursor.checkItemPlacement(
|
||||
|
@ -200,7 +197,7 @@ public class LoaderCursorTest {
|
|||
mIDP.numColumns = 4;
|
||||
mIDP.numHotseatIcons = 3;
|
||||
|
||||
// Hotseat items are only placed based on screenId
|
||||
// Hotseat mItems are only placed based on screenId
|
||||
assertTrue(mLoaderCursor.checkItemPlacement(
|
||||
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
|
||||
assertTrue(mLoaderCursor.checkItemPlacement(
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.util.Executors.createAndStartNewForegroundLooper;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.PagedView;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.LauncherLayoutBuilder;
|
||||
import com.android.launcher3.util.LauncherModelHelper;
|
||||
import com.android.launcher3.util.LauncherRoboTestRunner;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.ViewOnDrawExecutor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Tests to verify multiple callbacks in Loader
|
||||
*/
|
||||
@RunWith(LauncherRoboTestRunner.class)
|
||||
@LooperMode(Mode.PAUSED)
|
||||
public class ModelMultiCallbacksTest {
|
||||
|
||||
private LauncherModelHelper mModelHelper;
|
||||
|
||||
private ShadowPackageManager mSpm;
|
||||
private LooperExecutor mTempMainExecutor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
mModelHelper.installApp(TEST_PACKAGE);
|
||||
|
||||
mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager());
|
||||
|
||||
// Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
|
||||
// so that we can wait appropriately for the loader to complete.
|
||||
mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain"));
|
||||
ReflectionHelpers.setField(mModelHelper.getModel(), "mMainExecutor", mTempMainExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoCallbacks_loadedTogether() throws Exception {
|
||||
setupWorkspacePages(3);
|
||||
|
||||
MyCallbacks cb1 = spy(MyCallbacks.class);
|
||||
mModelHelper.getModel().addCallbacksAndLoad(cb1);
|
||||
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifySynchronouslyBound(3);
|
||||
|
||||
// Add a new callback
|
||||
cb1.reset();
|
||||
MyCallbacks cb2 = spy(MyCallbacks.class);
|
||||
cb2.mPageToBindSync = 2;
|
||||
mModelHelper.getModel().addCallbacksAndLoad(cb2);
|
||||
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifySynchronouslyBound(3);
|
||||
cb2.verifySynchronouslyBound(3);
|
||||
|
||||
// Remove callbacks
|
||||
cb1.reset();
|
||||
cb2.reset();
|
||||
|
||||
// No effect on callbacks when removing an callback
|
||||
mModelHelper.getModel().removeCallbacks(cb2);
|
||||
waitForLoaderAndTempMainThread();
|
||||
assertNull(cb1.mDeferredExecutor);
|
||||
assertNull(cb2.mDeferredExecutor);
|
||||
|
||||
// Reloading only loads registered callbacks
|
||||
mModelHelper.getModel().startLoader();
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifySynchronouslyBound(3);
|
||||
assertNull(cb2.mDeferredExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoCallbacks_receiveUpdates() throws Exception {
|
||||
setupWorkspacePages(1);
|
||||
|
||||
MyCallbacks cb1 = spy(MyCallbacks.class);
|
||||
MyCallbacks cb2 = spy(MyCallbacks.class);
|
||||
mModelHelper.getModel().addCallbacksAndLoad(cb1);
|
||||
mModelHelper.getModel().addCallbacksAndLoad(cb2);
|
||||
waitForLoaderAndTempMainThread();
|
||||
|
||||
cb1.verifyApps(TEST_PACKAGE);
|
||||
cb2.verifyApps(TEST_PACKAGE);
|
||||
|
||||
// Install package 1
|
||||
String pkg1 = "com.test.pkg1";
|
||||
mModelHelper.installApp(pkg1);
|
||||
mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle());
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifyApps(TEST_PACKAGE, pkg1);
|
||||
cb2.verifyApps(TEST_PACKAGE, pkg1);
|
||||
|
||||
// Install package 2
|
||||
String pkg2 = "com.test.pkg2";
|
||||
mModelHelper.installApp(pkg2);
|
||||
mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle());
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2);
|
||||
cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2);
|
||||
|
||||
// Uninstall package 2
|
||||
mSpm.removePackage(pkg1);
|
||||
mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle());
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifyApps(TEST_PACKAGE, pkg2);
|
||||
cb2.verifyApps(TEST_PACKAGE, pkg2);
|
||||
|
||||
// Unregister a callback and verify updates no longer received
|
||||
mModelHelper.getModel().removeCallbacks(cb2);
|
||||
mSpm.removePackage(pkg2);
|
||||
mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle());
|
||||
waitForLoaderAndTempMainThread();
|
||||
cb1.verifyApps(TEST_PACKAGE);
|
||||
cb2.verifyApps(TEST_PACKAGE, pkg2);
|
||||
}
|
||||
|
||||
private void waitForLoaderAndTempMainThread() throws Exception {
|
||||
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
|
||||
mTempMainExecutor.submit(() -> { }).get();
|
||||
}
|
||||
|
||||
private void setupWorkspacePages(int pageCount) throws Exception {
|
||||
// Create a layout with 3 pages
|
||||
LauncherLayoutBuilder builder = new LauncherLayoutBuilder();
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
builder.atWorkspace(1, 1, i).putApp(TEST_PACKAGE, TEST_PACKAGE);
|
||||
}
|
||||
mModelHelper.setupDefaultLayoutProvider(builder);
|
||||
}
|
||||
|
||||
private abstract static class MyCallbacks implements Callbacks {
|
||||
|
||||
final List<ItemInfo> mItems = new ArrayList<>();
|
||||
int mPageToBindSync = 0;
|
||||
int mPageBoundSync = PagedView.INVALID_PAGE;
|
||||
ViewOnDrawExecutor mDeferredExecutor;
|
||||
AppInfo[] mAppInfos;
|
||||
|
||||
MyCallbacks() { }
|
||||
|
||||
@Override
|
||||
public void onPageBoundSynchronously(int page) {
|
||||
mPageBoundSync = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
|
||||
mDeferredExecutor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
|
||||
mItems.addAll(shortcuts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindAllApplications(AppInfo[] apps) {
|
||||
mAppInfos = apps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPageToBindSynchronously() {
|
||||
return mPageToBindSync;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mItems.clear();
|
||||
mPageBoundSync = PagedView.INVALID_PAGE;
|
||||
mDeferredExecutor = null;
|
||||
mAppInfos = null;
|
||||
}
|
||||
|
||||
public void verifySynchronouslyBound(int totalItems) {
|
||||
// Verify that the requested page is bound synchronously
|
||||
assertEquals(mPageBoundSync, mPageToBindSync);
|
||||
assertEquals(mItems.size(), 1);
|
||||
assertEquals(mItems.get(0).screenId, mPageBoundSync);
|
||||
assertNotNull(mDeferredExecutor);
|
||||
|
||||
// Verify that all other pages are bound properly
|
||||
mDeferredExecutor.runAllTasks();
|
||||
assertEquals(mItems.size(), totalItems);
|
||||
}
|
||||
|
||||
public void verifyApps(String... apps) {
|
||||
assertEquals(apps.length, mAppInfos.length);
|
||||
assertEquals(Arrays.stream(mAppInfos)
|
||||
.map(ai -> ai.getTargetComponent().getPackageName())
|
||||
.collect(Collectors.toSet()),
|
||||
new HashSet<>(Arrays.asList(apps)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import org.robolectric.shadows.ShadowLauncherApps;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Extension of {@link ShadowLauncherApps} with missing shadow methods
|
||||
|
@ -93,4 +94,26 @@ public class LShadowLauncherApps extends ShadowLauncherApps {
|
|||
return RuntimeEnvironment.application.getPackageManager()
|
||||
.getApplicationInfo(packageName, flags);
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setPackage(packageName);
|
||||
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
|
||||
.stream()
|
||||
.map(ri -> getLauncherActivityInfo(ri.activityInfo))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public boolean hasShortcutHostPermission() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
|
||||
UserHandle user) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,19 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
|||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
|
@ -40,10 +46,14 @@ import org.mockito.ArgumentCaptor;
|
|||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -63,6 +73,13 @@ public class LauncherModelHelper {
|
|||
public static final int NO__ICON = -1;
|
||||
public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
|
||||
|
||||
// Authority for providing a dummy default-workspace-layout data.
|
||||
private static final String TEST_PROVIDER_AUTHORITY =
|
||||
LauncherModelHelper.class.getName().toLowerCase();
|
||||
private static final int DEFAULT_BITMAP_SIZE = 10;
|
||||
private static final int DEFAULT_GRID_SIZE = 4;
|
||||
|
||||
|
||||
private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
|
||||
public final TestLauncherProvider provider;
|
||||
|
||||
|
@ -285,4 +302,42 @@ public class LauncherModelHelper {
|
|||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a dummy provider to load the provided layout by default, next time the layout loads
|
||||
*/
|
||||
public void setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception {
|
||||
Context context = RuntimeEnvironment.application;
|
||||
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
|
||||
idp.numRows = idp.numColumns = idp.numHotseatIcons = DEFAULT_GRID_SIZE;
|
||||
idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
|
||||
|
||||
provider.setAllowLoadDefaultFavorites(true);
|
||||
Settings.Secure.putString(context.getContentResolver(),
|
||||
"launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
|
||||
|
||||
shadowOf(context.getPackageManager())
|
||||
.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
|
||||
TEST_PROVIDER_AUTHORITY;
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
builder.build(new OutputStreamWriter(bos));
|
||||
Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context);
|
||||
shadowOf(context.getContentResolver()).registerInputStream(layoutUri,
|
||||
new ByteArrayInputStream(bos.toByteArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates an apk install with a default main activity with same class and package name
|
||||
*/
|
||||
public void installApp(String component) throws NameNotFoundException {
|
||||
ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager());
|
||||
ComponentName cn = new ComponentName(component, component);
|
||||
spm.addActivityIfNotPresent(cn);
|
||||
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
|
||||
filter.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
filter.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
spm.addIntentFilterForActivity(cn, filter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,8 @@ public class DeleteDropTarget extends ButtonDropTarget {
|
|||
onAccessibilityDrop(null, item);
|
||||
ModelWriter modelWriter = mLauncher.getModelWriter();
|
||||
Runnable onUndoClicked = () -> {
|
||||
modelWriter.abortDelete(itemPage);
|
||||
mLauncher.setPageToBindSynchronously(itemPage);
|
||||
modelWriter.abortDelete();
|
||||
mLauncher.getUserEventDispatcher().logActionOnControl(TAP, UNDO);
|
||||
};
|
||||
Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
|
||||
|
|
|
@ -292,6 +292,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
private PopupDataProvider mPopupDataProvider;
|
||||
|
||||
private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
|
||||
private int mPageToBindSynchronously = PagedView.INVALID_PAGE;
|
||||
|
||||
// We only want to get the SharedPreferences once since it does an FS stat each time we get
|
||||
// it from the context.
|
||||
|
@ -348,7 +349,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
|
||||
LauncherAppState app = LauncherAppState.getInstance(this);
|
||||
mOldConfig = new Configuration(getResources().getConfiguration());
|
||||
mModel = app.setLauncher(this);
|
||||
mModel = app.getModel();
|
||||
mRotationHelper = new RotationHelper(this);
|
||||
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
|
||||
initDeviceProfile(idp);
|
||||
|
@ -386,22 +387,18 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
|
||||
// We only load the page synchronously if the user rotates (or triggers a
|
||||
// configuration change) while launcher is in the foreground
|
||||
int currentScreen = PagedView.INVALID_RESTORE_PAGE;
|
||||
int currentScreen = PagedView.INVALID_PAGE;
|
||||
if (savedInstanceState != null) {
|
||||
currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
|
||||
}
|
||||
mPageToBindSynchronously = currentScreen;
|
||||
|
||||
if (!mModel.startLoader(currentScreen)) {
|
||||
if (!mModel.addCallbacksAndLoad(this)) {
|
||||
if (!internalStateHandled) {
|
||||
// If we are not binding synchronously, show a fade in animation when
|
||||
// the first page bind completes.
|
||||
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
|
||||
}
|
||||
} else {
|
||||
// Pages bound synchronously.
|
||||
mWorkspace.setCurrentPage(currentScreen);
|
||||
|
||||
setWorkspaceLoading(true);
|
||||
}
|
||||
|
||||
// For handling default keys
|
||||
|
@ -521,15 +518,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
getStateManager().reapplyState(cancelCurrentAnimation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rebindModel() {
|
||||
int currentPage = mWorkspace.getNextPage();
|
||||
if (mModel.startLoader(currentPage)) {
|
||||
mWorkspace.setCurrentPage(currentPage);
|
||||
setWorkspaceLoading(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
|
||||
onIdpChanged(idp);
|
||||
|
@ -548,7 +536,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
// initialized properly.
|
||||
onSaveInstanceState(new Bundle());
|
||||
if (oldWallpaperProfile != getWallpaperDeviceProfile()) {
|
||||
rebindModel();
|
||||
mModel.rebindCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1543,13 +1531,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
mWorkspace.removeFolderListeners();
|
||||
PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
|
||||
|
||||
// Stop callbacks from LauncherModel
|
||||
// It's possible to receive onDestroy after a new Launcher activity has
|
||||
// been created. In this case, don't interfere with the new Launcher.
|
||||
if (mModel.isCurrentCallbacks(this)) {
|
||||
mModel.stopLoader();
|
||||
LauncherAppState.getInstance(this).setLauncher(null);
|
||||
}
|
||||
mModel.removeCallbacks(this);
|
||||
mRotationHelper.destroy();
|
||||
|
||||
try {
|
||||
|
@ -1956,12 +1938,22 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
mOnDeferredActivityLaunchCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the next page to bind synchronously on next bind.
|
||||
* @param page
|
||||
*/
|
||||
public void setPageToBindSynchronously(int page) {
|
||||
mPageToBindSynchronously = page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the method from LauncherModel.Callbacks.
|
||||
*/
|
||||
@Override
|
||||
public int getCurrentWorkspaceScreen() {
|
||||
if (mWorkspace != null) {
|
||||
public int getPageToBindSynchronously() {
|
||||
if (mPageToBindSynchronously != PagedView.INVALID_PAGE) {
|
||||
return mPageToBindSynchronously;
|
||||
} else if (mWorkspace != null) {
|
||||
return mWorkspace.getCurrentPage();
|
||||
} else {
|
||||
return 0;
|
||||
|
@ -2339,6 +2331,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
|
||||
public void onPageBoundSynchronously(int page) {
|
||||
mSynchronouslyBoundPage = page;
|
||||
mWorkspace.setCurrentPage(page);
|
||||
mPageToBindSynchronously = PagedView.INVALID_PAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2403,6 +2397,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
|||
// Since we are just resetting the current page without user interaction,
|
||||
// override the previous page so we don't log the page switch.
|
||||
mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
|
||||
mPageToBindSynchronously = PagedView.INVALID_PAGE;
|
||||
|
||||
// Cache one page worth of icons
|
||||
getViewCache().setCacheSize(R.layout.folder_application,
|
||||
|
|
|
@ -160,11 +160,6 @@ public class LauncherAppState {
|
|||
}
|
||||
}
|
||||
|
||||
LauncherModel setLauncher(Launcher launcher) {
|
||||
mModel.initialize(launcher);
|
||||
return mModel;
|
||||
}
|
||||
|
||||
public IconCache getIconCache() {
|
||||
return mIconCache;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
|
||||
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
|
@ -52,13 +58,12 @@ import com.android.launcher3.pm.UserCache;
|
|||
import com.android.launcher3.shortcuts.ShortcutRequest;
|
||||
import com.android.launcher3.util.IntSparseArrayMap;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.Thunk;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -67,12 +72,6 @@ import java.util.concurrent.CancellationException;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
|
||||
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
|
||||
/**
|
||||
* Maintains in-memory state of the Launcher. It is expected that there should be only one
|
||||
* LauncherModel object held in a static. Also provide APIs for updating the database state
|
||||
|
@ -83,11 +82,12 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
|
||||
static final String TAG = "Launcher.Model";
|
||||
|
||||
@Thunk final LauncherAppState mApp;
|
||||
@Thunk final Object mLock = new Object();
|
||||
@Thunk
|
||||
LoaderTask mLoaderTask;
|
||||
@Thunk boolean mIsLoaderTaskRunning;
|
||||
private final LauncherAppState mApp;
|
||||
private final Object mLock = new Object();
|
||||
private final LooperExecutor mMainExecutor = MAIN_EXECUTOR;
|
||||
|
||||
private LoaderTask mLoaderTask;
|
||||
private boolean mIsLoaderTaskRunning;
|
||||
|
||||
// Indicates whether the current model data is valid or not.
|
||||
// We start off with everything not loaded. After that, we assume that
|
||||
|
@ -100,7 +100,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
}
|
||||
}
|
||||
|
||||
@Thunk WeakReference<Callbacks> mCallbacks;
|
||||
private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
|
||||
|
||||
// < only access in worker thread >
|
||||
private final AllAppsList mBgAllAppsList;
|
||||
|
@ -141,9 +141,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
* Adds the provided items to the workspace.
|
||||
*/
|
||||
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
|
||||
Callbacks callbacks = getCallback();
|
||||
if (callbacks != null) {
|
||||
callbacks.preAddApps();
|
||||
for (Callbacks cb : getCallbacks()) {
|
||||
cb.preAddApps();
|
||||
}
|
||||
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
|
||||
}
|
||||
|
@ -153,16 +152,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
hasVerticalHotseat, verifyChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this as the current Launcher activity object for the loader.
|
||||
*/
|
||||
public void initialize(Callbacks callbacks) {
|
||||
synchronized (mLock) {
|
||||
Preconditions.assertUIThread();
|
||||
mCallbacks = new WeakReference<>(callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageChanged(String packageName, UserHandle user) {
|
||||
int op = PackageUpdatedTask.OP_UPDATE;
|
||||
|
@ -262,21 +251,19 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
}
|
||||
}
|
||||
} else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
|
||||
Launcher l = (Launcher) getCallback();
|
||||
l.reload();
|
||||
for (Callbacks cb : getCallbacks()) {
|
||||
if (cb instanceof Launcher) {
|
||||
((Launcher) cb).recreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forceReload() {
|
||||
forceReload(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param synchronousBindPage The page to bind first. Can pass -1 to use the current page.
|
||||
*/
|
||||
public void forceReload(int synchronousBindPage) {
|
||||
public void forceReload() {
|
||||
synchronized (mLock) {
|
||||
// Stop any existing loaders first, so they don't set mModelLoaded to true later
|
||||
stopLoader();
|
||||
|
@ -285,37 +272,77 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
|
||||
// Start the loader if launcher is already running, otherwise the loader will run,
|
||||
// the next time launcher starts
|
||||
Callbacks callbacks = getCallback();
|
||||
if (callbacks != null) {
|
||||
if (synchronousBindPage < 0) {
|
||||
synchronousBindPage = callbacks.getCurrentWorkspaceScreen();
|
||||
}
|
||||
startLoader(synchronousBindPage);
|
||||
if (hasCallbacks()) {
|
||||
startLoader();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCurrentCallbacks(Callbacks callbacks) {
|
||||
return (mCallbacks != null && mCallbacks.get() == callbacks);
|
||||
/**
|
||||
* Rebinds all existing callbacks with already loaded model
|
||||
*/
|
||||
public void rebindCallbacks() {
|
||||
if (hasCallbacks()) {
|
||||
startLoader();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing callback
|
||||
*/
|
||||
public void removeCallbacks(Callbacks callbacks) {
|
||||
synchronized (mCallbacksList) {
|
||||
Preconditions.assertUIThread();
|
||||
if (mCallbacksList.remove(callbacks)) {
|
||||
if (stopLoader()) {
|
||||
// Rebind existing callbacks
|
||||
startLoader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callbacks to receive model updates
|
||||
* @return true if workspace load was performed synchronously
|
||||
*/
|
||||
public boolean addCallbacksAndLoad(Callbacks callbacks) {
|
||||
synchronized (mLock) {
|
||||
addCallbacks(callbacks);
|
||||
return startLoader();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callbacks to receive model updates
|
||||
*/
|
||||
public void addCallbacks(Callbacks callbacks) {
|
||||
Preconditions.assertUIThread();
|
||||
synchronized (mCallbacksList) {
|
||||
mCallbacksList.add(callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
|
||||
* @return true if the page could be bound synchronously.
|
||||
*/
|
||||
public boolean startLoader(int synchronousBindPage) {
|
||||
public boolean startLoader() {
|
||||
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
|
||||
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
|
||||
synchronized (mLock) {
|
||||
// Don't bother to start the thread if we know it's not going to do anything
|
||||
if (mCallbacks != null && mCallbacks.get() != null) {
|
||||
final Callbacks oldCallbacks = mCallbacks.get();
|
||||
final Callbacks[] callbacksList = getCallbacks();
|
||||
if (callbacksList.length > 0) {
|
||||
// Clear any pending bind-runnables from the synchronized load process.
|
||||
MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
|
||||
for (Callbacks cb : callbacksList) {
|
||||
mMainExecutor.execute(cb::clearPendingBinds);
|
||||
}
|
||||
|
||||
// If there is already one running, tell it to stop.
|
||||
stopLoader();
|
||||
LoaderResults loaderResults = new LoaderResults(mApp, mBgDataModel,
|
||||
mBgAllAppsList, synchronousBindPage, mCallbacks);
|
||||
LoaderResults loaderResults = new LoaderResults(
|
||||
mApp, mBgDataModel, mBgAllAppsList, callbacksList, mMainExecutor);
|
||||
if (mModelLoaded && !mIsLoaderTaskRunning) {
|
||||
// Divide the set of loaded items into those that we are binding synchronously,
|
||||
// and everything else that is to be bound normally (asynchronously).
|
||||
|
@ -336,14 +363,17 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
|
||||
/**
|
||||
* If there is already a loader task running, tell it to stop.
|
||||
* @return true if an existing loader was stopped.
|
||||
*/
|
||||
public void stopLoader() {
|
||||
public boolean stopLoader() {
|
||||
synchronized (mLock) {
|
||||
LoaderTask oldTask = mLoaderTask;
|
||||
mLoaderTask = null;
|
||||
if (oldTask != null) {
|
||||
oldTask.stopLocked();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,7 +528,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
}
|
||||
|
||||
public void enqueueModelUpdateTask(ModelUpdateTask task) {
|
||||
task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
|
||||
task.init(mApp, this, mBgDataModel, mBgAllAppsList, mMainExecutor);
|
||||
MODEL_EXECUTOR.execute(task);
|
||||
}
|
||||
|
||||
|
@ -572,7 +602,21 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
|||
mBgDataModel.dump(prefix, fd, writer, args);
|
||||
}
|
||||
|
||||
public Callbacks getCallback() {
|
||||
return mCallbacks != null ? mCallbacks.get() : null;
|
||||
/**
|
||||
* Returns true if there are any callbacks attached to the model
|
||||
*/
|
||||
public boolean hasCallbacks() {
|
||||
synchronized (mCallbacksList) {
|
||||
return !mCallbacksList.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of currently attached callbacks
|
||||
*/
|
||||
public Callbacks[] getCallbacks() {
|
||||
synchronized (mCallbacksList) {
|
||||
return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
|
|||
private static final String TAG = "PagedView";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
protected static final int INVALID_PAGE = -1;
|
||||
public static final int INVALID_PAGE = -1;
|
||||
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
|
||||
|
||||
public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
|
||||
|
@ -84,8 +84,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
|
|||
private static final int MIN_SNAP_VELOCITY = 1500;
|
||||
private static final int MIN_FLING_VELOCITY = 250;
|
||||
|
||||
public static final int INVALID_RESTORE_PAGE = -1001;
|
||||
|
||||
private boolean mFreeScroll = false;
|
||||
|
||||
protected int mFlingThresholdVelocity;
|
||||
|
|
|
@ -70,6 +70,7 @@ import com.android.launcher3.icons.BitmapInfo;
|
|||
import com.android.launcher3.icons.BitmapRenderer;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.LoaderResults;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
@ -377,7 +378,7 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
|
|||
if (!mModel.isModelLoaded()) {
|
||||
Log.d(TAG, "Workspace not loaded, loading now");
|
||||
mModel.startLoaderForResults(
|
||||
new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
|
||||
new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return mBgDataModel.workspaceItems;
|
||||
|
|
|
@ -18,9 +18,7 @@ package com.android.launcher3.model;
|
|||
|
||||
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
|
||||
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
|
@ -32,12 +30,13 @@ import com.android.launcher3.LauncherModel.CallbackTask;
|
|||
import com.android.launcher3.PagedView;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.LooperIdleLock;
|
||||
import com.android.launcher3.util.ViewOnDrawExecutor;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
|
@ -49,40 +48,29 @@ public abstract class BaseLoaderResults {
|
|||
protected static final int INVALID_SCREEN_ID = -1;
|
||||
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
|
||||
|
||||
protected final Executor mUiExecutor;
|
||||
protected final LooperExecutor mUiExecutor;
|
||||
|
||||
protected final LauncherAppState mApp;
|
||||
protected final BgDataModel mBgDataModel;
|
||||
private final AllAppsList mBgAllAppsList;
|
||||
protected final int mPageToBindFirst;
|
||||
|
||||
protected final WeakReference<Callbacks> mCallbacks;
|
||||
private final Callbacks[] mCallbacksList;
|
||||
|
||||
private int mMyBindingId;
|
||||
|
||||
public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
|
||||
mUiExecutor = MAIN_EXECUTOR;
|
||||
AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
|
||||
mUiExecutor = uiExecutor;
|
||||
mApp = app;
|
||||
mBgDataModel = dataModel;
|
||||
mBgAllAppsList = allAppsList;
|
||||
mPageToBindFirst = pageToBindFirst;
|
||||
mCallbacks = callbacks == null ? new WeakReference<>(null) : callbacks;
|
||||
mCallbacksList = callbacksList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds all loaded data to actual views on the main thread.
|
||||
*/
|
||||
public void bindWorkspace() {
|
||||
Callbacks callbacks = mCallbacks.get();
|
||||
// Don't use these two variables in any of the callback runnables.
|
||||
// Otherwise we hold a reference to them.
|
||||
if (callbacks == null) {
|
||||
// This launcher has exited and nobody bothered to tell us. Just bail.
|
||||
Log.w(TAG, "LoaderTask running with no launcher");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save a copy of all the bg-thread collections
|
||||
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
|
||||
|
@ -96,97 +84,9 @@ public abstract class BaseLoaderResults {
|
|||
mMyBindingId = mBgDataModel.lastBindId;
|
||||
}
|
||||
|
||||
final int currentScreen;
|
||||
{
|
||||
int currScreen = mPageToBindFirst != PagedView.INVALID_RESTORE_PAGE
|
||||
? mPageToBindFirst : callbacks.getCurrentWorkspaceScreen();
|
||||
if (currScreen >= orderedScreenIds.size()) {
|
||||
// There may be no workspace screens (just hotseat items and an empty page).
|
||||
currScreen = PagedView.INVALID_RESTORE_PAGE;
|
||||
}
|
||||
currentScreen = currScreen;
|
||||
}
|
||||
final boolean validFirstPage = currentScreen >= 0;
|
||||
final int currentScreenId =
|
||||
validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
|
||||
|
||||
// Separate the items that are on the current screen, and all the other remaining items
|
||||
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
|
||||
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
|
||||
|
||||
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
|
||||
otherWorkspaceItems);
|
||||
filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
|
||||
otherAppWidgets);
|
||||
final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
|
||||
sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
|
||||
sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
|
||||
|
||||
// Tell the workspace that we're about to start binding items
|
||||
executeCallbacksTask(c -> {
|
||||
c.clearPendingBinds();
|
||||
c.startBinding();
|
||||
}, mUiExecutor);
|
||||
|
||||
// Bind workspace screens
|
||||
executeCallbacksTask(c -> c.bindScreens(orderedScreenIds), mUiExecutor);
|
||||
|
||||
Executor mainExecutor = mUiExecutor;
|
||||
// Load items on the current page.
|
||||
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
|
||||
bindAppWidgets(currentAppWidgets, mainExecutor);
|
||||
// In case of validFirstPage, only bind the first screen, and defer binding the
|
||||
// remaining screens after first onDraw (and an optional the fade animation whichever
|
||||
// happens later).
|
||||
// This ensures that the first screen is immediately visible (eg. during rotation)
|
||||
// In case of !validFirstPage, bind all pages one after other.
|
||||
final Executor deferredExecutor =
|
||||
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
|
||||
|
||||
executeCallbacksTask(c -> c.finishFirstPageBind(
|
||||
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
|
||||
|
||||
bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
|
||||
bindAppWidgets(otherAppWidgets, deferredExecutor);
|
||||
// Tell the workspace that we're done binding items
|
||||
executeCallbacksTask(c -> c.finishBindingItems(mPageToBindFirst), deferredExecutor);
|
||||
|
||||
if (validFirstPage) {
|
||||
executeCallbacksTask(c -> {
|
||||
// We are loading synchronously, which means, some of the pages will be
|
||||
// bound after first draw. Inform the callbacks that page binding is
|
||||
// not complete, and schedule the remaining pages.
|
||||
if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
|
||||
c.onPageBoundSynchronously(currentScreen);
|
||||
}
|
||||
c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
|
||||
|
||||
}, mUiExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
|
||||
final Executor executor) {
|
||||
// Bind the workspace items
|
||||
int N = workspaceItems.size();
|
||||
for (int i = 0; i < N; i += ITEMS_CHUNK) {
|
||||
final int start = i;
|
||||
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
|
||||
executeCallbacksTask(
|
||||
c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
|
||||
executor);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindAppWidgets(ArrayList<LauncherAppWidgetInfo> appWidgets, Executor executor) {
|
||||
int N;// Bind the widgets, one at a time
|
||||
N = appWidgets.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
final ItemInfo widget = appWidgets.get(i);
|
||||
executeCallbacksTask(
|
||||
c -> c.bindItems(Collections.singletonList(widget), false), executor);
|
||||
for (Callbacks cb : mCallbacksList) {
|
||||
new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
|
||||
workspaceItems, appWidgets, orderedScreenIds).bind();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,19 +106,155 @@ public abstract class BaseLoaderResults {
|
|||
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
|
||||
return;
|
||||
}
|
||||
Callbacks callbacks = mCallbacks.get();
|
||||
if (callbacks != null) {
|
||||
task.execute(callbacks);
|
||||
for (Callbacks cb : mCallbacksList) {
|
||||
task.execute(cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LooperIdleLock newIdleLock(Object lock) {
|
||||
LooperIdleLock idleLock = new LooperIdleLock(lock, Looper.getMainLooper());
|
||||
LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
|
||||
// If we are not binding or if the main looper is already idle, there is no reason to wait
|
||||
if (mCallbacks.get() == null || Looper.getMainLooper().getQueue().isIdle()) {
|
||||
if (mUiExecutor.getLooper().getQueue().isIdle()) {
|
||||
idleLock.queueIdle();
|
||||
}
|
||||
return idleLock;
|
||||
}
|
||||
|
||||
private static class WorkspaceBinder {
|
||||
|
||||
private final Executor mUiExecutor;
|
||||
private final Callbacks mCallbacks;
|
||||
|
||||
private final LauncherAppState mApp;
|
||||
private final BgDataModel mBgDataModel;
|
||||
|
||||
private final int mMyBindingId;
|
||||
private final ArrayList<ItemInfo> mWorkspaceItems;
|
||||
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
|
||||
private final IntArray mOrderedScreenIds;
|
||||
|
||||
|
||||
WorkspaceBinder(Callbacks callbacks,
|
||||
Executor uiExecutor,
|
||||
LauncherAppState app,
|
||||
BgDataModel bgDataModel,
|
||||
int myBindingId,
|
||||
ArrayList<ItemInfo> workspaceItems,
|
||||
ArrayList<LauncherAppWidgetInfo> appWidgets,
|
||||
IntArray orderedScreenIds) {
|
||||
mCallbacks = callbacks;
|
||||
mUiExecutor = uiExecutor;
|
||||
mApp = app;
|
||||
mBgDataModel = bgDataModel;
|
||||
mMyBindingId = myBindingId;
|
||||
mWorkspaceItems = workspaceItems;
|
||||
mAppWidgets = appWidgets;
|
||||
mOrderedScreenIds = orderedScreenIds;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
final int currentScreen;
|
||||
{
|
||||
// Create an anonymous scope to calculate currentScreen as it has to be a
|
||||
// final variable.
|
||||
int currScreen = mCallbacks.getPageToBindSynchronously();
|
||||
if (currScreen >= mOrderedScreenIds.size()) {
|
||||
// There may be no workspace screens (just hotseat items and an empty page).
|
||||
currScreen = PagedView.INVALID_PAGE;
|
||||
}
|
||||
currentScreen = currScreen;
|
||||
}
|
||||
final boolean validFirstPage = currentScreen >= 0;
|
||||
final int currentScreenId =
|
||||
validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
|
||||
|
||||
// Separate the items that are on the current screen, and all the other remaining items
|
||||
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
|
||||
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
|
||||
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
|
||||
|
||||
filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems,
|
||||
otherWorkspaceItems);
|
||||
filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets,
|
||||
otherAppWidgets);
|
||||
final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
|
||||
sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
|
||||
sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
|
||||
|
||||
// Tell the workspace that we're about to start binding items
|
||||
executeCallbacksTask(c -> {
|
||||
c.clearPendingBinds();
|
||||
c.startBinding();
|
||||
}, mUiExecutor);
|
||||
|
||||
// Bind workspace screens
|
||||
executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
|
||||
|
||||
Executor mainExecutor = mUiExecutor;
|
||||
// Load items on the current page.
|
||||
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
|
||||
bindAppWidgets(currentAppWidgets, mainExecutor);
|
||||
// In case of validFirstPage, only bind the first screen, and defer binding the
|
||||
// remaining screens after first onDraw (and an optional the fade animation whichever
|
||||
// happens later).
|
||||
// This ensures that the first screen is immediately visible (eg. during rotation)
|
||||
// In case of !validFirstPage, bind all pages one after other.
|
||||
final Executor deferredExecutor =
|
||||
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
|
||||
|
||||
executeCallbacksTask(c -> c.finishFirstPageBind(
|
||||
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
|
||||
|
||||
bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
|
||||
bindAppWidgets(otherAppWidgets, deferredExecutor);
|
||||
// Tell the workspace that we're done binding items
|
||||
executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor);
|
||||
|
||||
if (validFirstPage) {
|
||||
executeCallbacksTask(c -> {
|
||||
// We are loading synchronously, which means, some of the pages will be
|
||||
// bound after first draw. Inform the mCallbacks that page binding is
|
||||
// not complete, and schedule the remaining pages.
|
||||
c.onPageBoundSynchronously(currentScreen);
|
||||
c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
|
||||
|
||||
}, mUiExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindWorkspaceItems(
|
||||
final ArrayList<ItemInfo> workspaceItems, final Executor executor) {
|
||||
// Bind the workspace items
|
||||
int count = workspaceItems.size();
|
||||
for (int i = 0; i < count; i += ITEMS_CHUNK) {
|
||||
final int start = i;
|
||||
final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
|
||||
executeCallbacksTask(
|
||||
c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
|
||||
executor);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) {
|
||||
// Bind the widgets, one at a time
|
||||
int count = appWidgets.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final ItemInfo widget = appWidgets.get(i);
|
||||
executeCallbacksTask(
|
||||
c -> c.bindItems(Collections.singletonList(widget), false), executor);
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
|
||||
executor.execute(() -> {
|
||||
if (mMyBindingId != mBgDataModel.lastBindId) {
|
||||
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
|
||||
return;
|
||||
}
|
||||
task.execute(mCallbacks);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,17 +20,16 @@ import android.util.Log;
|
|||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.LauncherModel.CallbackTask;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.widget.WidgetListRowEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
|
@ -78,13 +77,9 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask {
|
|||
* Schedules a {@param task} to be executed on the current callbacks.
|
||||
*/
|
||||
public final void scheduleCallbackTask(final CallbackTask task) {
|
||||
final Callbacks callbacks = mModel.getCallback();
|
||||
mUiExecutor.execute(() -> {
|
||||
Callbacks cb = mModel.getCallback();
|
||||
if (callbacks == cb && cb != null) {
|
||||
task.execute(callbacks);
|
||||
}
|
||||
});
|
||||
for (final Callbacks cb : mModel.getCallbacks()) {
|
||||
mUiExecutor.execute(() -> task.execute(cb));
|
||||
}
|
||||
}
|
||||
|
||||
public ModelWriter getModelWriter() {
|
||||
|
|
|
@ -436,9 +436,10 @@ public class BgDataModel {
|
|||
}
|
||||
|
||||
public interface Callbacks {
|
||||
void rebindModel();
|
||||
|
||||
int getCurrentWorkspaceScreen();
|
||||
/**
|
||||
* Returns the page number to bind first, synchronously if possible or -1
|
||||
*/
|
||||
int getPageToBindSynchronously();
|
||||
void clearPendingBinds();
|
||||
void startBinding();
|
||||
void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
|
||||
|
|
|
@ -18,14 +18,15 @@ package com.android.launcher3.model;
|
|||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
/**
|
||||
* Utility class to preload LauncherModel
|
||||
*/
|
||||
|
@ -50,7 +51,7 @@ public class ModelPreload implements ModelUpdateTask {
|
|||
@Override
|
||||
public final void run() {
|
||||
mModel.startLoaderForResultsIfNotLoaded(
|
||||
new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
|
||||
new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
|
||||
Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
|
||||
onComplete(mModel.isModelLoaded());
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.android.launcher3.Utilities;
|
|||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.util.ContentWriter;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
|
||||
|
@ -350,12 +349,15 @@ public class ModelWriter {
|
|||
mDeleteRunnables.clear();
|
||||
}
|
||||
|
||||
public void abortDelete(int pageToBindFirst) {
|
||||
/**
|
||||
* Aborts a previous delete operation pending commit
|
||||
*/
|
||||
public void abortDelete() {
|
||||
mPreparingToUndo = false;
|
||||
mDeleteRunnables.clear();
|
||||
// We do a full reload here instead of just a rebind because Folders change their internal
|
||||
// state when dragging an item out, which clobbers the rebind unless we load from the DB.
|
||||
mModel.forceReload(pageToBindFirst);
|
||||
mModel.forceReload();
|
||||
}
|
||||
|
||||
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
|
||||
|
@ -472,7 +474,7 @@ public class ModelWriter {
|
|||
}
|
||||
|
||||
void verifyModel() {
|
||||
if (!mVerifyChanges || mModel.getCallback() == null) {
|
||||
if (!mVerifyChanges || !mModel.hasCallbacks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -488,11 +490,9 @@ public class ModelWriter {
|
|||
// Bound model has not changed during the job
|
||||
return;
|
||||
}
|
||||
|
||||
// Bound model was changed between submitting the job and executing the job
|
||||
Callbacks callbacks = mModel.getCallback();
|
||||
if (callbacks != null) {
|
||||
callbacks.rebindModel();
|
||||
}
|
||||
mModel.rebindCallbacks();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import android.view.View;
|
|||
import android.view.View.OnAttachStateChangeListener;
|
||||
import android.view.ViewTreeObserver.OnDrawListener;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -118,7 +120,11 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
|
|||
return mCompleted;
|
||||
}
|
||||
|
||||
protected void runAllTasks() {
|
||||
/**
|
||||
* Executes all tasks immediately
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void runAllTasks() {
|
||||
for (final Runnable r : mTasks) {
|
||||
r.run();
|
||||
}
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.widget.WidgetListRowEntry;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
@ -31,8 +33,13 @@ import java.util.HashMap;
|
|||
public class LoaderResults extends BaseLoaderResults {
|
||||
|
||||
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
|
||||
super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
|
||||
AllAppsList allAppsList, Callbacks[] callbacks) {
|
||||
this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
|
||||
}
|
||||
|
||||
public LoaderResults(LauncherAppState app, BgDataModel dataModel,
|
||||
AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
|
||||
super(app, dataModel, allAppsList, callbacks, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue