diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java index f8ac010ee1..7bc34cf91f 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -30,11 +30,8 @@ import android.content.pm.PackageInstaller.SessionParams; import com.android.launcher3.FolderInfo; import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.icons.BitmapInfo; -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; @@ -46,8 +43,6 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.LooperMode.Mode; -import java.util.ArrayList; - /** * Tests for layout parser for remote layout */ @@ -120,18 +115,6 @@ public class DefaultLayoutProviderTest { } private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception { - mModelHelper.setupDefaultLayoutProvider(builder); - - LoaderResults results = new LoaderResults( - LauncherAppState.getInstance(mTargetContext), - mModelHelper.getBgDataModel(), - mModelHelper.getAllAppsList(), - new Callbacks[0]); - LoaderTask task = new LoaderTask( - LauncherAppState.getInstance(mTargetContext), - mModelHelper.getAllAppsList(), - mModelHelper.getBgDataModel(), - results); - Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get(); + mModelHelper.setupDefaultLayoutProvider(builder).loadModelSync(); } } diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java index f16ed33cfe..76cb74707f 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java @@ -127,6 +127,10 @@ public class LShadowLauncherApps extends ShadowLauncherApps { @Override protected List getShortcutConfigActivityList(String packageName, UserHandle user) { - return Collections.emptyList(); + Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName); + return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0) + .stream() + .map(ri -> getLauncherActivityInfo(ri.activityInfo)) + .collect(Collectors.toList()); } } diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java new file mode 100644 index 0000000000..0e7c1deba0 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowTypeface.java @@ -0,0 +1,38 @@ +/* + * 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.shadows; + +import android.graphics.Typeface; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowTypeface; + +/** + * Extension of {@link ShadowTypeface} with missing shadow methods + */ +@Implements(Typeface.class) +public class LShadowTypeface extends ShadowTypeface { + + @Implementation + public static Typeface create(Typeface family, int weight, boolean italic) { + int style = italic ? Typeface.ITALIC : Typeface.NORMAL; + if (weight >= 400) { + style |= Typeface.BOLD; + } + return create(family, style); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java new file mode 100644 index 0000000000..d60251c901 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowWallpaperManager.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 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.shadows; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.app.WallpaperManager; +import android.content.Context; + +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.shadows.ShadowUserManager; +import org.robolectric.shadows.ShadowWallpaperManager; + +/** + * Extension of {@link ShadowUserManager} with missing shadow methods + */ +@Implements(WallpaperManager.class) +public class LShadowWallpaperManager extends ShadowWallpaperManager { + + @Implementation + protected static WallpaperManager getInstance(Context context) { + return context.getSystemService(WallpaperManager.class); + } + + /** + * Remove this once the fix for + * https://github.com/robolectric/robolectric/issues/5285 + * is available + */ + public static void initializeMock() { + WallpaperManager wm = mock(WallpaperManager.class); + ShadowApplication shadowApplication = Shadows.shadowOf(RuntimeEnvironment.application); + shadowApplication.setSystemService(Context.WALLPAPER_SERVICE, wm); + doReturn(0).when(wm).getDesiredMinimumWidth(); + doReturn(0).when(wm).getDesiredMinimumHeight(); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java new file mode 100644 index 0000000000..131f69136c --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowOverrides.java @@ -0,0 +1,61 @@ +/* + * 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.shadows; + +import android.content.Context; + +import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider; +import com.android.launcher3.util.ResourceBasedOverride; +import com.android.launcher3.util.ResourceBasedOverride.Overrides; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.util.ReflectionHelpers.ClassParameter; + +import java.util.HashMap; +import java.util.Map; + +/** + * Shadow for {@link Overrides} to provide custom overrides for test + */ +@Implements(value = Overrides.class, isInAndroidSdk = false) +public class ShadowOverrides { + + private static Map sProviderMap = new HashMap<>(); + + @Implementation + public static T getObject( + Class clazz, Context context, int resId) { + ObjectProvider provider = sProviderMap.get(clazz); + if (provider != null) { + return provider.get(context); + } + return Shadow.directlyOn(Overrides.class, "getObject", + ClassParameter.from(Class.class, clazz), + ClassParameter.from(Context.class, context), + ClassParameter.from(int.class, resId)); + } + + public static void setProvider(Class clazz, ObjectProvider provider) { + sProviderMap.put(clazz, provider); + } + + public static void clearProvider() { + sProviderMap.clear(); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java new file mode 100644 index 0000000000..209bae06a8 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java @@ -0,0 +1,207 @@ +/* + * 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.ui; + +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; + +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.mock; + +import android.app.Activity; +import android.content.Context; +import android.os.SystemClock; +import android.provider.Settings; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerProperties; +import android.view.View; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderPagedView; +import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.shadows.ShadowOverrides; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; +import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetsFullSheet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; +import org.robolectric.shadows.ShadowLooper; +import org.robolectric.util.ReflectionHelpers; + +/** + * Tests scroll behavior at various Launcher UI components + */ +@RunWith(LauncherRoboTestRunner.class) +@LooperMode(Mode.PAUSED) +public class LauncherUIScrollTest { + + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + private LauncherModelHelper mModelHelper; + + private LauncherLayoutBuilder mLayoutBuilder; + + @Before + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mTargetContext = RuntimeEnvironment.application; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + ShadowOverrides.setProvider(UserEventDispatcher.class, + c -> mock(UserEventDispatcher.class)); + + Settings.Global.putFloat(mTargetContext.getContentResolver(), + Settings.Global.WINDOW_ANIMATION_SCALE, 0); + + mModelHelper.installApp(TEST_PACKAGE); + // LayoutBuilder with 3 workspace pages + mLayoutBuilder = new LauncherLayoutBuilder() + .atWorkspace(0, mIdp.numRows - 1, 0).putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, mIdp.numRows - 1, 1).putApp(TEST_PACKAGE, TEST_PACKAGE) + .atWorkspace(0, mIdp.numRows - 1, 2).putApp(TEST_PACKAGE, TEST_PACKAGE); + } + + @Test + public void testWorkspacePagesBound() throws Exception { + // Verify that the workspace if bound synchronously + Launcher launcher = loadLauncher(); + assertEquals(3, launcher.getWorkspace().getPageCount()); + assertEquals(0, launcher.getWorkspace().getCurrentPage()); + + launcher.dispatchGenericMotionEvent(createScrollEvent(-1)); + assertNotEquals("Workspace was not scrolled", + 0, launcher.getWorkspace().getNextPage()); + } + + @Test + public void testAllAppsScroll() throws Exception { + // Install 100 apps + for (int i = 0; i < 100; i++) { + mModelHelper.installApp(TEST_PACKAGE + i); + } + + // Bind and open all-apps + Launcher launcher = loadLauncher(); + launcher.getStateManager().goToState(LauncherState.ALL_APPS, false); + doLayout(launcher); + + int currentScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY(); + launcher.dispatchGenericMotionEvent(createScrollEvent(-1)); + int newScroll = launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY(); + + assertNotEquals("All Apps was not scrolled", currentScroll, newScroll); + assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage()); + } + + @Test + public void testWidgetsListScroll() throws Exception { + // Install 100 widgets + for (int i = 0; i < 100; i++) { + mModelHelper.installCustomShortcut(TEST_PACKAGE + i, "shortcutProvider"); + } + + // Bind and open widgets + Launcher launcher = loadLauncher(); + WidgetsFullSheet widgets = WidgetsFullSheet.show(launcher, false); + doLayout(launcher); + + int currentScroll = widgets.getRecyclerView().getCurrentScrollY(); + launcher.dispatchGenericMotionEvent(createScrollEvent(-1)); + int newScroll = widgets.getRecyclerView().getCurrentScrollY(); + assertNotEquals("Widgets was not scrolled", currentScroll, newScroll); + assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage()); + } + + @Test + public void testFolderPageScroll() throws Exception { + // Add a folder with multiple icons + FolderBuilder fb = mLayoutBuilder.atWorkspace(mIdp.numColumns / 2, mIdp.numRows / 2, 0) + .putFolder(0); + for (int i = 0; i < 100; i++) { + fb.addApp(TEST_PACKAGE, TEST_PACKAGE); + } + + // Bind and open folder + Launcher launcher = loadLauncher(); + doLayout(launcher); + launcher.getWorkspace().getFirstMatch((i, v) -> v instanceof FolderIcon).performClick(); + ShadowLooper.idleMainLooper(); + doLayout(launcher); + FolderPagedView folderPages = Folder.getOpen(launcher).getContent(); + + assertEquals(0, folderPages.getNextPage()); + launcher.dispatchGenericMotionEvent(createScrollEvent(-1)); + assertNotEquals("Folder page was not scrolled", 0, folderPages.getNextPage()); + assertEquals("Workspace was scrolled", 0, launcher.getWorkspace().getNextPage()); + } + + private Launcher loadLauncher() throws Exception { + mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder).loadModelSync(); + + Launcher launcher = Robolectric.buildActivity(Launcher.class).setup().get(); + doLayout(launcher); + ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor"); + if (executor != null) { + executor.runAllTasks(); + } + return launcher; + } + + private static void doLayout(Activity activity) { + DeviceProfile dp = InvariantDeviceProfile.INSTANCE + .get(RuntimeEnvironment.application).portraitProfile; + View view = activity.getWindow().getDecorView(); + view.measure(makeMeasureSpec(dp.widthPx, EXACTLY), makeMeasureSpec(dp.heightPx, EXACTLY)); + view.layout(0, 0, dp.widthPx, dp.heightPx); + ShadowLooper.idleMainLooper(); + } + + private static MotionEvent createScrollEvent(int scroll) { + DeviceProfile dp = InvariantDeviceProfile.INSTANCE + .get(RuntimeEnvironment.application).portraitProfile; + + final PointerProperties[] pointerProperties = new PointerProperties[1]; + pointerProperties[0] = new PointerProperties(); + pointerProperties[0].id = 0; + final MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1]; + coords[0] = new MotionEvent.PointerCoords(); + coords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, scroll); + coords[0].x = dp.widthPx / 2; + coords[0].y = dp.heightPx / 2; + + final long time = SystemClock.uptimeMillis(); + return MotionEvent.obtain(time, time, MotionEvent.ACTION_SCROLL, 1, + pointerProperties, coords, 0, 0, 1.0f, 1.0f, 0, 0, + InputDevice.SOURCE_CLASS_POINTER, 0); + } + +} diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java index 20b1453b6f..d593d84778 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.util; +import static android.content.Intent.ACTION_CREATE_SHORTCUT; + import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -44,6 +46,7 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.pm.UserCache; import org.mockito.ArgumentCaptor; @@ -61,6 +64,7 @@ import java.io.OutputStreamWriter; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Function; @@ -345,7 +349,8 @@ public class LauncherModelHelper { /** * Sets up a dummy provider to load the provided layout by default, next time the layout loads */ - public void setupDefaultLayoutProvider(LauncherLayoutBuilder builder) throws Exception { + public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder) + throws Exception { Context context = RuntimeEnvironment.application; InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context); idp.numRows = idp.numColumns = idp.numHotseatIcons = DEFAULT_GRID_SIZE; @@ -363,22 +368,52 @@ public class LauncherModelHelper { Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, context); shadowOf(context.getContentResolver()).registerInputStream(layoutUri, new ByteArrayInputStream(bos.toByteArray())); + return this; } /** * 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); + installApp(component, component, filter); + } + + /** + * Simulates a custom shortcut install + */ + public void installCustomShortcut(String pkg, String clazz) throws NameNotFoundException { + installApp(pkg, clazz, new IntentFilter(ACTION_CREATE_SHORTCUT)); + } + + private void installApp(String pkg, String clazz, IntentFilter filter) + throws NameNotFoundException { + ShadowPackageManager spm = shadowOf(RuntimeEnvironment.application.getPackageManager()); + ComponentName cn = new ComponentName(pkg, clazz); + spm.addActivityIfNotPresent(cn); + filter.addCategory(Intent.CATEGORY_DEFAULT); spm.addIntentFilterForActivity(cn, filter); } + /** + * Loads the model in memory synchronously + */ + public void loadModelSync() throws ExecutionException, InterruptedException { + // 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. + ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.UI_HELPER_EXECUTOR); + + Callbacks mockCb = mock(Callbacks.class); + getModel().addCallbacksAndLoad(mockCb); + + Executors.MODEL_EXECUTOR.submit(() -> { }).get(); + Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get(); + ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.MAIN_EXECUTOR); + getModel().removeCallbacks(mockCb); + } + /** * An extension of LauncherProvider backed up by in-memory database. */ diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java index 6277c66d2c..744b478b2a 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java @@ -21,10 +21,13 @@ import com.android.launcher3.shadows.LShadowAppWidgetManager; import com.android.launcher3.shadows.LShadowBackupManager; import com.android.launcher3.shadows.LShadowBitmap; import com.android.launcher3.shadows.LShadowLauncherApps; +import com.android.launcher3.shadows.LShadowTypeface; import com.android.launcher3.shadows.LShadowUserManager; +import com.android.launcher3.shadows.LShadowWallpaperManager; import com.android.launcher3.shadows.ShadowDeviceFlag; import com.android.launcher3.shadows.ShadowLooperExecutor; import com.android.launcher3.shadows.ShadowMainThreadInitializedObject; +import com.android.launcher3.shadows.ShadowOverrides; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import org.junit.runners.model.InitializationError; @@ -49,9 +52,12 @@ public class LauncherRoboTestRunner extends RobolectricTestRunner { LShadowLauncherApps.class, LShadowBitmap.class, LShadowBackupManager.class, + LShadowTypeface.class, + LShadowWallpaperManager.class, ShadowLooperExecutor.class, ShadowMainThreadInitializedObject.class, ShadowDeviceFlag.class, + ShadowOverrides.class }; public LauncherRoboTestRunner(Class testClass) throws InitializationError { @@ -78,6 +84,9 @@ public class LauncherRoboTestRunner extends RobolectricTestRunner { // Disable plugins PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class)); + + // Initialize mock wallpaper manager + LShadowWallpaperManager.initializeMock(); } @Override @@ -86,6 +95,7 @@ public class LauncherRoboTestRunner extends RobolectricTestRunner { ShadowLog.stream = null; ShadowMainThreadInitializedObject.resetInitializedObjects(); + ShadowOverrides.clearProvider(); } } } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 7d7739eb12..5e47e2ff73 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -16,6 +16,14 @@ package com.android.launcher3; +import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; +import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; +import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; +import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; +import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY; +import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO; + import android.animation.LayoutTransition; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; @@ -42,14 +50,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.ScrollView; -import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; -import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; -import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; -import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; -import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY; -import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO; - import androidx.annotation.Nullable; import com.android.launcher3.anim.Interpolators; @@ -63,6 +63,7 @@ import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds; import com.android.launcher3.touch.PortraitPagedViewHandler; import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; @@ -1369,10 +1370,6 @@ public abstract class PagedView extends ViewGrou if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: { - Launcher launcher = Launcher.getLauncher(getContext()); - if (launcher != null) { - AbstractFloatingView.closeAllOpenViews(launcher); - } // Handle mouse (or ext. device) by shifting the page depending on the scroll final float vscroll; final float hscroll; @@ -1383,8 +1380,8 @@ public abstract class PagedView extends ViewGrou vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); } - if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) { - return true; + if (!canScroll(Math.abs(vscroll), Math.abs(hscroll))) { + return false; } if (hscroll != 0 || vscroll != 0) { boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0) @@ -1402,8 +1399,13 @@ public abstract class PagedView extends ViewGrou return super.onGenericMotionEvent(event); } - protected boolean isVerticalScrollable() { - return true; + /** + * Returns true if the paged view can scroll for the provided vertical and horizontal + * scroll values + */ + protected boolean canScroll(float absVScroll, float absHScroll) { + ActivityContext ac = ActivityContext.lookupContext(getContext()); + return (ac == null || AbstractFloatingView.getTopOpenView(ac) == null); } private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java index ab4cb6b8db..f640c3e8d0 100644 --- a/src/com/android/launcher3/allapps/AllAppsPagedView.java +++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java @@ -83,7 +83,7 @@ public class AllAppsPagedView extends PagedView { } @Override - protected boolean isVerticalScrollable() { - return false; + protected boolean canScroll(float absVScroll, float absHScroll) { + return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll); } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 365e76fa2b..c241dc2148 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -1648,6 +1648,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } + public FolderPagedView getContent() { + return mContent; + } + private void logEditFolderLabel() { LauncherEvent launcherEvent = LauncherEvent.newBuilder() .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD)) diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index b83609e680..3d72b49b44 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -115,6 +115,7 @@ public class FolderAnimationManager { */ public AnimatorSet getAnimator() { final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); + mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams(); ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); final List itemsInPreview = getPreviewIconsOnPage(0); diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index c6d62f8bc7..dcd0e144bc 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -16,6 +16,9 @@ package com.android.launcher3.folder; +import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER; + import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; @@ -27,6 +30,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; @@ -258,7 +262,7 @@ public class FolderPagedView extends PagedView { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - mPageIndicator.setScroll(l, mMaxScroll); + if (mMaxScroll > 0) mPageIndicator.setScroll(l, mMaxScroll); } /** @@ -614,6 +618,12 @@ public class FolderPagedView extends PagedView { } } + @Override + protected boolean canScroll(float absVScroll, float absHScroll) { + return AbstractFloatingView.getTopOpenViewWithType(mFolder.mLauncher, + TYPE_ALL & ~TYPE_FOLDER) == null; + } + public int itemsPerPage() { return mOrganizer.getMaxItemsPerPage(); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 62904ae4a4..fc0997b198 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -45,8 +45,6 @@ import android.util.LongSparseArray; import android.util.MutableInt; import android.util.TimingLogger; -import androidx.annotation.VisibleForTesting; - import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; @@ -282,8 +280,7 @@ public class LoaderTask implements Runnable { this.notify(); } - @VisibleForTesting - void loadWorkspace(List allDeepShortcuts) { + private void loadWorkspace(List allDeepShortcuts) { loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI); } diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java index b07a4f461d..d7a814a439 100644 --- a/src/com/android/launcher3/widget/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java @@ -87,6 +87,11 @@ public class WidgetsFullSheet extends BaseWidgetSheet onWidgetsBound(); } + @VisibleForTesting + public WidgetsRecyclerView getRecyclerView() { + return mRecyclerView; + } + @Override protected Pair getAccessibilityTarget() { return Pair.create(mRecyclerView, getContext().getString(