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
This commit is contained in:
parent
8451542a25
commit
0e7a338e8b
|
@ -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">
|
||||
|
||||
<GridView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -119,8 +120,7 @@
|
|||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textColorHint="@drawable/all_apps_search_hint"
|
||||
android:textSize="16sp"
|
||||
android:translationY="24dp" />
|
||||
android:textSize="16sp" />
|
||||
|
||||
<include layout="@layout/all_apps_fast_scroller" />
|
||||
</com.android.launcher3.allapps.AllAppsContainerView>
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<DisplayOption> 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<DisplayOption> 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<DisplayOption> 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<DisplayOption> 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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -117,10 +117,12 @@ public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher>
|
|||
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));
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue