From 0e7a338e8b8ca90c30bf7e4daf055febf4d24aaa Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 13 Apr 2020 17:15:05 -0700 Subject: [PATCH] Fixing SecondaryDisplay Launcher > All apps layout broken in landscpae UI > Crash when manager profile is added > Wrong icon size on low resolution Change-Id: If01dacf9f62a0384ebd8b31b62500178416d3ab4 --- res/layout/secondary_launcher.xml | 6 +- .../config/robolectric.properties | 1 + .../secondarydisplay/SDWorkModeTest.java | 133 +++++++++++++++++ .../LShadowApplicationPackageManager.java | 36 +++++ .../shadows/LShadowLauncherApps.java | 12 +- src/com/android/launcher3/DeviceProfile.java | 131 +++++++++++++---- .../launcher3/InvariantDeviceProfile.java | 139 ++++++++++++------ .../launcher3/allapps/WorkModeSwitch.java | 20 +-- .../SecondaryDisplayLauncher.java | 15 +- .../secondarydisplay/SecondaryDragLayer.java | 6 +- .../android/launcher3/views/ArrowTipView.java | 16 +- 11 files changed, 398 insertions(+), 117 deletions(-) create mode 100644 robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml index 98cfc349af..fdf4446c96 100644 --- a/res/layout/secondary_launcher.xml +++ b/res/layout/secondary_launcher.xml @@ -17,7 +17,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/drag_layer" > + android:id="@+id/drag_layer" + android:padding="@dimen/dynamic_grid_edge_margin"> + android:textSize="16sp" /> diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties index 0579400655..c162255bb4 100644 --- a/robolectric_tests/config/robolectric.properties +++ b/robolectric_tests/config/robolectric.properties @@ -1,5 +1,6 @@ sdk=29 shadows= \ + com.android.launcher3.shadows.LShadowApplicationPackageManager \ com.android.launcher3.shadows.LShadowAppPredictionManager \ com.android.launcher3.shadows.LShadowAppWidgetManager \ com.android.launcher3.shadows.LShadowBackupManager \ diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java new file mode 100644 index 0000000000..0ca5ce6f44 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java @@ -0,0 +1,133 @@ +/* + * 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.secondarydisplay; + +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; +import static com.android.launcher3.util.LauncherUIHelper.doLayout; +import static com.android.launcher3.util.Preconditions.assertNotNull; + +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.allapps.AllAppsPagedView; +import com.android.launcher3.allapps.AllAppsRecyclerView; +import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.shadows.ShadowOverrides; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowUserManager; + +/** + * Tests for {@link SecondaryDisplayLauncher} with work profile + */ +@RunWith(RobolectricTestRunner.class) +@LooperMode(Mode.PAUSED) +public class SDWorkModeTest { + + private static final int SYSTEM_USER = 0; + private static final int FLAG_SYSTEM = 0x00000800; + private static final int WORK_PROFILE_ID = 10; + private static final int FLAG_PROFILE = 0x00001000; + + 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); + mLayoutBuilder = new LauncherLayoutBuilder(); + } + + @Test + public void testAllAppsList_noWorkProfile() throws Exception { + SecondaryDisplayLauncher launcher = loadLauncher(); + launcher.showAppDrawer(true); + doLayout(launcher); + + verifyRecyclerViewCount(launcher.getAppsView().getActiveRecyclerView()); + } + + @Test + public void testAllAppsList_workProfile() throws Exception { + ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class)); + sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM); + sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE); + + SecondaryDisplayLauncher launcher = loadLauncher(); + launcher.showAppDrawer(true); + doLayout(launcher); + + AllAppsRecyclerView rv1 = launcher.getAppsView().getActiveRecyclerView(); + verifyRecyclerViewCount(rv1); + + assertNotNull(launcher.getAppsView().getWorkModeSwitch()); + assertTrue(launcher.getAppsView().getRecyclerViewContainer() instanceof AllAppsPagedView); + + AllAppsPagedView pagedView = + (AllAppsPagedView) launcher.getAppsView().getRecyclerViewContainer(); + pagedView.snapToPageImmediately(1); + doLayout(launcher); + + AllAppsRecyclerView rv2 = launcher.getAppsView().getActiveRecyclerView(); + verifyRecyclerViewCount(rv2); + assertNotSame(rv1, rv2); + } + + private SecondaryDisplayLauncher loadLauncher() throws Exception { + // Install 100 apps + for (int i = 0; i < 100; i++) { + mModelHelper.installApp(TEST_PACKAGE + i); + } + mModelHelper.setupDefaultLayoutProvider(new LauncherLayoutBuilder()).loadModelSync(); + SecondaryDisplayLauncher launcher = + Robolectric.buildActivity(SecondaryDisplayLauncher.class).setup().get(); + doLayout(launcher); + return launcher; + } + + private void verifyRecyclerViewCount(AllAppsRecyclerView rv) { + int childCount = rv.getChildCount(); + assertTrue(childCount > 0); + assertTrue(childCount < 100); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java new file mode 100644 index 0000000000..da8b91986f --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowApplicationPackageManager.java @@ -0,0 +1,36 @@ +/* + * 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.os.Process; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowApplicationPackageManager; + +/** + * Shadow for {@link ShadowApplicationPackageManager} which create mock predictors + */ +@Implements(className = "android.app.ApplicationPackageManager") +public class LShadowApplicationPackageManager extends ShadowApplicationPackageManager { + + @Implementation + public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { + return Process.myUserHandle().equals(user) ? label : "Work " + label; + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java index 76cb74707f..6a6f0fbe70 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java @@ -30,7 +30,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; -import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; @@ -80,14 +79,15 @@ public class LShadowLauncherApps extends ShadowLauncherApps { protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { ResolveInfo ri = RuntimeEnvironment.application.getPackageManager() .resolveActivity(intent, 0); - return ri == null ? null : getLauncherActivityInfo(ri.activityInfo); + return ri == null ? null : getLauncherActivityInfo(ri.activityInfo, user); } - public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) { + public LauncherActivityInfo getLauncherActivityInfo( + ActivityInfo activityInfo, UserHandle user) { return callConstructor(LauncherActivityInfo.class, ClassParameter.from(Context.class, RuntimeEnvironment.application), ClassParameter.from(ActivityInfo.class, activityInfo), - ClassParameter.from(UserHandle.class, Process.myUserHandle())); + ClassParameter.from(UserHandle.class, user)); } @Implementation @@ -104,7 +104,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps { .setPackage(packageName); return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0) .stream() - .map(ri -> getLauncherActivityInfo(ri.activityInfo)) + .map(ri -> getLauncherActivityInfo(ri.activityInfo, user)) .collect(Collectors.toList()); } @@ -130,7 +130,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps { Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName); return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0) .stream() - .map(ri -> getLauncherActivityInfo(ri.activityInfo)) + .map(ri -> getLauncherActivityInfo(ri.activityInfo, user)) .collect(Collectors.toList()); } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index fc3c9fd1cc..a5f98c0cdc 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -22,7 +22,6 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.util.DisplayMetrics; import android.view.Surface; import com.android.launcher3.CellLayout.ContainerType; @@ -34,6 +33,7 @@ import com.android.launcher3.util.DefaultDisplay; public class DeviceProfile { public final InvariantDeviceProfile inv; + private final DefaultDisplay.Info mInfo; // Device properties public final boolean isTablet; @@ -133,9 +133,9 @@ public class DeviceProfile { public DotRenderer mDotRendererWorkSpace; public DotRenderer mDotRendererAllApps; - public DeviceProfile(Context context, InvariantDeviceProfile inv, - Point minSize, Point maxSize, - int width, int height, boolean isLandscape, boolean isMultiWindowMode) { + public DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info, + Point minSize, Point maxSize, int width, int height, boolean isLandscape, + boolean isMultiWindowMode, boolean transposeLayoutWithOrientation) { this.inv = inv; this.isLandscape = isLandscape; @@ -152,8 +152,8 @@ public class DeviceProfile { availableHeightPx = maxSize.y; } + mInfo = info; Resources res = context.getResources(); - DisplayMetrics dm = res.getDisplayMetrics(); // Constants from resources isTablet = res.getBoolean(R.bool.is_tablet); @@ -163,8 +163,7 @@ public class DeviceProfile { boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0; // Some more constants - transposeLayoutWithOrientation = - res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation); + this.transposeLayoutWithOrientation = transposeLayoutWithOrientation; context = getContext(context, isVerticalBarLayout() ? Configuration.ORIENTATION_LANDSCAPE @@ -207,13 +206,14 @@ public class DeviceProfile { res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); // Add a bit of space between nav bar and hotseat in vertical bar layout. hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; - hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout() + hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) + + (isVerticalBarLayout() ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx) : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx)); // Calculate all of the remaining variables. - updateAvailableDimensions(dm, res); + updateAvailableDimensions(res); // Now that we have all of the variables calculated, we can tune certain sizes. if (!isVerticalBarLayout() && isPhone && isTallDevice) { @@ -227,7 +227,7 @@ public class DeviceProfile { hotseatBarBottomPaddingPx += extraSpace; // Recalculate the available dimensions using the new hotseat size. - updateAvailableDimensions(dm, res); + updateAvailableDimensions(res); } updateWorkspacePadding(); @@ -239,12 +239,21 @@ public class DeviceProfile { IconShape.DEFAULT_PATH_SIZE); } - public DeviceProfile copy(Context context) { + public Builder toBuilder(Context context) { Point size = new Point(availableWidthPx, availableHeightPx); - return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape, - isMultiWindowMode); + return new Builder(context, inv, mInfo) + .setSizeRange(size, size) + .setSize(widthPx, heightPx) + .setMultiWindowMode(isMultiWindowMode); } + public DeviceProfile copy(Context context) { + return toBuilder(context).build(); + } + + /** + * TODO: Move this to the builder as part of setMultiWindowMode + */ public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { // We take the minimum sizes of this profile and it's multi-window variant to ensure that // the system decor is always excluded. @@ -253,8 +262,11 @@ public class DeviceProfile { // In multi-window mode, we can have widthPx = availableWidthPx // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' // widthPx and heightPx values where it's needed. - DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, - isLandscape, true); + DeviceProfile profile = toBuilder(context) + .setSizeRange(mwSize, mwSize) + .setSize(mwSize.x, mwSize.y) + .setMultiWindowMode(true) + .build(); // If there isn't enough vertical cell padding with the labels displayed, hide the labels. float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx @@ -289,27 +301,30 @@ public class DeviceProfile { iconTextSizePx = 0; iconDrawablePaddingPx = 0; cellHeightPx = iconSizePx; + autoResizeAllAppsCells(); + } - // In normal cases, All Apps cell height should equal the Workspace cell height. - // Since we are removing labels from the Workspace, we need to manually compute the - // All Apps cell height. + /** + * Re-computes the all-apps cell size to be independent of workspace + */ + public void autoResizeAllAppsCells() { int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1); allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx + Utilities.calculateTextHeight(allAppsIconTextSizePx) + topBottomPadding * 2; } - private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { - updateIconSize(1f, res, dm); + private void updateAvailableDimensions(Resources res) { + updateIconSize(1f, res); // Check to see if the icons fit within the available height. If not, then scale down. float usedHeight = (cellHeightPx * inv.numRows); int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); if (usedHeight > maxHeight) { float scale = maxHeight / usedHeight; - updateIconSize(scale, res, dm); + updateIconSize(scale, res); } - updateAvailableFolderCellDimensions(dm, res); + updateAvailableFolderCellDimensions(res); } /** @@ -317,12 +332,13 @@ public class DeviceProfile { * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. */ - private void updateIconSize(float scale, Resources res, DisplayMetrics dm) { + private void updateIconSize(float scale, Resources res) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; - iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale)); - iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); + iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics) + * scale)); + iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale); iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); cellHeightPx = iconSizePx + iconDrawablePaddingPx @@ -340,8 +356,8 @@ public class DeviceProfile { // All apps if (allAppsHasDifferentNumColumns()) { - allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm); - allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm); + allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics); + allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics); allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y; allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; } else { @@ -381,12 +397,12 @@ public class DeviceProfile { folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2; } - private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) { + private void updateAvailableFolderCellDimensions(Resources res) { int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); - updateFolderCellSize(1f, dm, res); + updateFolderCellSize(1f, res); // Don't let the folder get too close to the edges of the screen. int folderMargin = edgeMarginPx * 2; @@ -405,12 +421,12 @@ public class DeviceProfile { float scale = Math.min(scaleX, scaleY); if (scale < 1f) { - updateFolderCellSize(scale, dm, res); + updateFolderCellSize(scale, res); } } - private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) { - folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale); + private void updateFolderCellSize(float scale, Resources res) { + folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale); folderChildTextSizePx = (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); @@ -627,4 +643,55 @@ public class DeviceProfile { */ void onDeviceProfileChanged(DeviceProfile dp); } + + public static class Builder { + private Context mContext; + private InvariantDeviceProfile mInv; + private DefaultDisplay.Info mInfo; + + private Point mMinSize, mMaxSize; + private int mWidth, mHeight; + + private boolean mIsLandscape; + private boolean mIsMultiWindowMode = false; + private boolean mTransposeLayoutWithOrientation; + + public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) { + mContext = context; + mInv = inv; + mInfo = info; + mTransposeLayoutWithOrientation = context.getResources() + .getBoolean(R.bool.hotseat_transpose_layout_with_orientation); + } + + public Builder setSizeRange(Point minSize, Point maxSize) { + mMinSize = minSize; + mMaxSize = maxSize; + return this; + } + + public Builder setSize(int width, int height) { + mWidth = width; + mHeight = height; + mIsLandscape = mWidth > mHeight; + return this; + } + + public Builder setMultiWindowMode(boolean isMultiWindowMode) { + mIsMultiWindowMode = isMultiWindowMode; + return this; + } + + public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) { + mTransposeLayoutWithOrientation = transposeLayoutWithOrientation; + return this; + } + + public DeviceProfile build() { + return new DeviceProfile(mContext, mInv, mInfo, mMinSize, mMaxSize, + mWidth, mHeight, mIsLandscape, mIsMultiWindowMode, + mTransposeLayoutWithOrientation); + } + } + } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index d2d08639d4..0a1fad1d7d 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -159,10 +160,12 @@ public class InvariantDeviceProfile { @TargetApi(23) private InvariantDeviceProfile(Context context) { - String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) - ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) - : null; - initGrid(context, gridName); + String gridName = getCurrentGridName(context); + String newGridName = initGrid(context, gridName); + if (!newGridName.equals(gridName)) { + Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply(); + } + mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); mOverlayMonitor = new OverlayMonitor(context); @@ -178,17 +181,36 @@ public class InvariantDeviceProfile { } } -/** + /** * This constructor should NOT have any monitors by design. */ public InvariantDeviceProfile(Context context, Display display) { - initGrid(context, null, new Info(display)); + // Ensure that the main device profile is initialized + InvariantDeviceProfile originalProfile = INSTANCE.get(context); + String gridName = getCurrentGridName(context); + + // Get the display info based on default display and interpolate it to existing display + DisplayOption defaultDisplayOption = invDistWeightedInterpolate( + DefaultDisplay.INSTANCE.get(context).getInfo(), + getPredefinedDeviceProfiles(context, gridName)); + + Info myInfo = new Info(display); + DisplayOption myDisplayOption = invDistWeightedInterpolate( + myInfo, getPredefinedDeviceProfiles(context, gridName)); + + DisplayOption result = new DisplayOption(defaultDisplayOption.grid) + .add(myDisplayOption); + result.iconSize = defaultDisplayOption.iconSize; + result.landscapeIconSize = defaultDisplayOption.landscapeIconSize; + result.allAppsIconSize = Math.min( + defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize); + initGrid(context, myInfo, result); } public static String getCurrentGridName(Context context) { - return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) - ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) - : null; + SharedPreferences prefs = Utilities.getPrefs(context); + return prefs.getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? prefs.getString(KEY_IDP_GRID_NAME, null) : null; } /** @@ -203,27 +225,17 @@ public class InvariantDeviceProfile { } private String initGrid(Context context, String gridName) { - return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo()); + DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo(); + ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); + + DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions); + initGrid(context, displayInfo, displayOption); + return displayOption.grid.name; } - private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) { - Point smallestSize = new Point(displayInfo.smallestSize); - Point largestSize = new Point(displayInfo.largestSize); - - ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); - // This guarantees that width < height - float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), - displayInfo.metrics); - float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), - displayInfo.metrics); - // Sort the profiles based on the closeness to the device size - Collections.sort(allOptions, (a, b) -> - Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), - dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps))); - DisplayOption interpolatedDisplayOption = - invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); - - GridOption closestProfile = allOptions.get(0).grid; + private void initGrid( + Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) { + GridOption closestProfile = displayOption.grid; numRows = closestProfile.numRows; numColumns = closestProfile.numColumns; numHotseatIcons = closestProfile.numHotseatIcons; @@ -236,21 +248,16 @@ public class InvariantDeviceProfile { mExtraAttrs = closestProfile.extraAttrs; - if (!closestProfile.name.equals(gridName)) { - Utilities.getPrefs(context).edit() - .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply(); - } - - iconSize = interpolatedDisplayOption.iconSize; + iconSize = displayOption.iconSize; iconShapePath = getIconShapePath(context); - landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; + landscapeIconSize = displayOption.landscapeIconSize; iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics); - iconTextSize = interpolatedDisplayOption.iconTextSize; + iconTextSize = displayOption.iconTextSize; fillResIconDpi = getLauncherIconDensity(iconBitmapSize); if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) { - allAppsIconSize = interpolatedDisplayOption.allAppsIconSize; - allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize; + allAppsIconSize = displayOption.allAppsIconSize; + allAppsIconTextSize = displayOption.allAppsIconTextSize; } else { allAppsIconSize = iconSize; allAppsIconTextSize = iconTextSize; @@ -266,10 +273,12 @@ public class InvariantDeviceProfile { int smallSide = Math.min(realSize.x, realSize.y); int largeSide = Math.max(realSize.x, realSize.y); - landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize, - largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */); - portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize, - smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */); + DeviceProfile.Builder builder = new DeviceProfile.Builder(context, this, displayInfo) + .setSizeRange(new Point(displayInfo.smallestSize), + new Point(displayInfo.largestSize)); + + landscapeProfile = builder.setSize(largeSide, smallSide).build(); + portraitProfile = builder.setSize(smallSide, largeSide).build(); // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects @@ -283,8 +292,6 @@ public class InvariantDeviceProfile { ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName()); defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null); - - return closestProfile.name; } @Nullable @@ -453,6 +460,41 @@ public class InvariantDeviceProfile { return (float) Math.hypot(x1 - x0, y1 - y0); } + @VisibleForTesting + static DisplayOption invDistWeightedInterpolate( + DefaultDisplay.Info displayInfo, ArrayList points) { + Point smallestSize = new Point(displayInfo.smallestSize); + Point largestSize = new Point(displayInfo.largestSize); + + // This guarantees that width < height + float width = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), + displayInfo.metrics); + float height = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), + displayInfo.metrics); + + // Sort the profiles based on the closeness to the device size + Collections.sort(points, (a, b) -> + Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), + dist(width, height, b.minWidthDps, b.minHeightDps))); + + GridOption closestOption = points.get(0).grid; + float weights = 0; + + DisplayOption p = points.get(0); + if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) { + return p; + } + + DisplayOption out = new DisplayOption(closestOption); + for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { + p = points.get(i); + float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); + weights += w; + out.add(new DisplayOption().add(p).multiply(w)); + } + return out.multiply(1.0f / weights); + } + @VisibleForTesting static DisplayOption invDistWeightedInterpolate(float width, float height, ArrayList points) { @@ -573,7 +615,6 @@ public class InvariantDeviceProfile { private static final class DisplayOption { private final GridOption grid; - private final String name; private final float minWidthDps; private final float minHeightDps; private final boolean canBeDefault; @@ -590,7 +631,6 @@ public class InvariantDeviceProfile { TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ProfileDisplayOption); - name = a.getString(R.styleable.ProfileDisplayOption_name); minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0); minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0); canBeDefault = a.getBoolean( @@ -609,8 +649,11 @@ public class InvariantDeviceProfile { } DisplayOption() { - grid = null; - name = null; + this(null); + } + + DisplayOption(GridOption grid) { + this.grid = grid; minWidthDps = 0; minHeightDps = 0; canBeDefault = false; diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index 33262b6860..6692af5d9a 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -18,6 +18,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.AsyncTask; @@ -30,7 +31,6 @@ import android.view.ViewConfiguration; import android.widget.Switch; import com.android.launcher3.Insettable; -import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.pm.UserCache; @@ -79,9 +79,8 @@ public class WorkModeSwitch extends Switch implements Insettable { @Override public void toggle() { - Launcher launcher = Launcher.getLauncher(getContext()); // don't show tip if user uses toggle - launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply(); + Utilities.getPrefs(getContext()).edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply(); trySetQuietModeEnabledToAllProfilesAsync(isChecked()); } @@ -203,9 +202,8 @@ public class WorkModeSwitch extends Switch implements Insettable { } private boolean shouldShowWorkSwitch() { - Launcher launcher = Launcher.getLauncher(getContext()); - return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher) - || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") + return Utilities.ATLEAST_P && (hasShortcutsPermission(getContext()) + || getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE") == PackageManager.PERMISSION_GRANTED); } @@ -213,12 +211,14 @@ public class WorkModeSwitch extends Switch implements Insettable { * Shows a work tip on the Nth work tab open */ public void showTipifNeeded() { - Launcher launcher = Launcher.getLauncher(getContext()); - int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD); + Context context = getContext(); + SharedPreferences prefs = Utilities.getPrefs(context); + int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD); if (tipCounter < 0) return; if (tipCounter == 0) { - new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop()); + new ArrowTipView(context) + .show(context.getString(R.string.work_switch_tip), getTop()); } - launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply(); + prefs.edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply(); } } diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index f1f271f9fd..dd6fc49426 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -89,17 +89,16 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity if (mDragLayer != null) { return; } - InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this); InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay()); - // Pick the device profile with the smaller icon size so that the cached icons are - // shown properly - if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) { - mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this); - } else { - mDeviceProfile = currentDisplayIdp.getDeviceProfile(this); - } + // Disable transpose layout and use multi-window mode so that the icons are scaled properly + mDeviceProfile = currentDisplayIdp.getDeviceProfile(this) + .toBuilder(this) + .setMultiWindowMode(true) + .setTransposeLayoutWithOrientation(false) + .build(); + mDeviceProfile.autoResizeAllAppsCells(); setContentView(R.layout.secondary_launcher); mDragLayer = findViewById(R.id.drag_layer); diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java index e35e884c77..40630d3c2e 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java @@ -117,10 +117,12 @@ public class SecondaryDragLayer extends BaseDragLayer if (child == mAppsView) { int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx + grid.cellLayoutPaddingLeftRightPx); - int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding; + int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding; int appsWidth = Math.min(width, maxWidth); - int appsHeight = Math.round(appsWidth * (float) height / (float) width); + + int maxHeight = grid.allAppsCellHeightPx * idp.numAllAppsColumns + padding; + int appsHeight = Math.min(height, maxHeight); mAppsView.measure( makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY)); diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index 60470dc57f..a7575d12ba 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -32,7 +32,7 @@ import android.widget.TextView; import androidx.core.content.ContextCompat; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Launcher; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.dragndrop.DragLayer; @@ -48,13 +48,13 @@ public class ArrowTipView extends AbstractFloatingView { private static final long SHOW_DURATION_MS = 300; private static final long HIDE_DURATION_MS = 100; - protected final Launcher mLauncher; + protected final BaseDraggingActivity mActivity; private final Handler mHandler = new Handler(); private Runnable mOnClosed; public ArrowTipView(Context context) { super(context, null, 0); - mLauncher = Launcher.getLauncher(context); + mActivity = BaseDraggingActivity.fromContext(context); init(context); } @@ -75,11 +75,11 @@ public class ArrowTipView extends AbstractFloatingView { .setStartDelay(0) .setDuration(HIDE_DURATION_MS) .setInterpolator(Interpolators.ACCEL) - .withEndAction(() -> mLauncher.getDragLayer().removeView(this)) + .withEndAction(() -> mActivity.getDragLayer().removeView(this)) .start(); } else { animate().cancel(); - mLauncher.getDragLayer().removeView(this); + mActivity.getDragLayer().removeView(this); } if (mOnClosed != null) mOnClosed.run(); mIsOpen = false; @@ -126,12 +126,12 @@ public class ArrowTipView extends AbstractFloatingView { */ public ArrowTipView show(String text, int top) { ((TextView) findViewById(R.id.text)).setText(text); - mLauncher.getDragLayer().addView(this); + mActivity.getDragLayer().addView(this); DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); params.gravity = Gravity.CENTER_HORIZONTAL; - params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left; - params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right; + params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left; + params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right; post(() -> setY(top - getHeight())); setAlpha(0); animate()