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"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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
|
<GridView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -119,8 +120,7 @@
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:textColorHint="@drawable/all_apps_search_hint"
|
android:textColorHint="@drawable/all_apps_search_hint"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp" />
|
||||||
android:translationY="24dp" />
|
|
||||||
|
|
||||||
<include layout="@layout/all_apps_fast_scroller" />
|
<include layout="@layout/all_apps_fast_scroller" />
|
||||||
</com.android.launcher3.allapps.AllAppsContainerView>
|
</com.android.launcher3.allapps.AllAppsContainerView>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
sdk=29
|
sdk=29
|
||||||
shadows= \
|
shadows= \
|
||||||
|
com.android.launcher3.shadows.LShadowApplicationPackageManager \
|
||||||
com.android.launcher3.shadows.LShadowAppPredictionManager \
|
com.android.launcher3.shadows.LShadowAppPredictionManager \
|
||||||
com.android.launcher3.shadows.LShadowAppWidgetManager \
|
com.android.launcher3.shadows.LShadowAppWidgetManager \
|
||||||
com.android.launcher3.shadows.LShadowBackupManager \
|
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.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.os.Process;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
|
|
||||||
|
@ -80,14 +79,15 @@ public class LShadowLauncherApps extends ShadowLauncherApps {
|
||||||
protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
|
protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
|
||||||
ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
|
ResolveInfo ri = RuntimeEnvironment.application.getPackageManager()
|
||||||
.resolveActivity(intent, 0);
|
.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,
|
return callConstructor(LauncherActivityInfo.class,
|
||||||
ClassParameter.from(Context.class, RuntimeEnvironment.application),
|
ClassParameter.from(Context.class, RuntimeEnvironment.application),
|
||||||
ClassParameter.from(ActivityInfo.class, activityInfo),
|
ClassParameter.from(ActivityInfo.class, activityInfo),
|
||||||
ClassParameter.from(UserHandle.class, Process.myUserHandle()));
|
ClassParameter.from(UserHandle.class, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Implementation
|
@Implementation
|
||||||
|
@ -104,7 +104,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps {
|
||||||
.setPackage(packageName);
|
.setPackage(packageName);
|
||||||
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
|
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
|
||||||
.stream()
|
.stream()
|
||||||
.map(ri -> getLauncherActivityInfo(ri.activityInfo))
|
.map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps {
|
||||||
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
|
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName);
|
||||||
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
|
return RuntimeEnvironment.application.getPackageManager().queryIntentActivities(intent, 0)
|
||||||
.stream()
|
.stream()
|
||||||
.map(ri -> getLauncherActivityInfo(ri.activityInfo))
|
.map(ri -> getLauncherActivityInfo(ri.activityInfo, user))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import android.content.res.Resources;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import com.android.launcher3.CellLayout.ContainerType;
|
import com.android.launcher3.CellLayout.ContainerType;
|
||||||
|
@ -34,6 +33,7 @@ import com.android.launcher3.util.DefaultDisplay;
|
||||||
public class DeviceProfile {
|
public class DeviceProfile {
|
||||||
|
|
||||||
public final InvariantDeviceProfile inv;
|
public final InvariantDeviceProfile inv;
|
||||||
|
private final DefaultDisplay.Info mInfo;
|
||||||
|
|
||||||
// Device properties
|
// Device properties
|
||||||
public final boolean isTablet;
|
public final boolean isTablet;
|
||||||
|
@ -133,9 +133,9 @@ public class DeviceProfile {
|
||||||
public DotRenderer mDotRendererWorkSpace;
|
public DotRenderer mDotRendererWorkSpace;
|
||||||
public DotRenderer mDotRendererAllApps;
|
public DotRenderer mDotRendererAllApps;
|
||||||
|
|
||||||
public DeviceProfile(Context context, InvariantDeviceProfile inv,
|
public DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
|
||||||
Point minSize, Point maxSize,
|
Point minSize, Point maxSize, int width, int height, boolean isLandscape,
|
||||||
int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
|
boolean isMultiWindowMode, boolean transposeLayoutWithOrientation) {
|
||||||
|
|
||||||
this.inv = inv;
|
this.inv = inv;
|
||||||
this.isLandscape = isLandscape;
|
this.isLandscape = isLandscape;
|
||||||
|
@ -152,8 +152,8 @@ public class DeviceProfile {
|
||||||
availableHeightPx = maxSize.y;
|
availableHeightPx = maxSize.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mInfo = info;
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
DisplayMetrics dm = res.getDisplayMetrics();
|
|
||||||
|
|
||||||
// Constants from resources
|
// Constants from resources
|
||||||
isTablet = res.getBoolean(R.bool.is_tablet);
|
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;
|
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
|
||||||
|
|
||||||
// Some more constants
|
// Some more constants
|
||||||
transposeLayoutWithOrientation =
|
this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
|
||||||
res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
|
|
||||||
|
|
||||||
context = getContext(context, isVerticalBarLayout()
|
context = getContext(context, isVerticalBarLayout()
|
||||||
? Configuration.ORIENTATION_LANDSCAPE
|
? Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
@ -207,13 +206,14 @@ public class DeviceProfile {
|
||||||
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
|
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
|
||||||
// Add a bit of space between nav bar and hotseat in vertical bar layout.
|
// Add a bit of space between nav bar and hotseat in vertical bar layout.
|
||||||
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
|
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
|
||||||
hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout()
|
hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
|
||||||
|
+ (isVerticalBarLayout()
|
||||||
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
|
? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
|
||||||
: (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
|
: (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
|
||||||
+ hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
|
+ hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
|
||||||
|
|
||||||
// Calculate all of the remaining variables.
|
// 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.
|
// Now that we have all of the variables calculated, we can tune certain sizes.
|
||||||
if (!isVerticalBarLayout() && isPhone && isTallDevice) {
|
if (!isVerticalBarLayout() && isPhone && isTallDevice) {
|
||||||
|
@ -227,7 +227,7 @@ public class DeviceProfile {
|
||||||
hotseatBarBottomPaddingPx += extraSpace;
|
hotseatBarBottomPaddingPx += extraSpace;
|
||||||
|
|
||||||
// Recalculate the available dimensions using the new hotseat size.
|
// Recalculate the available dimensions using the new hotseat size.
|
||||||
updateAvailableDimensions(dm, res);
|
updateAvailableDimensions(res);
|
||||||
}
|
}
|
||||||
updateWorkspacePadding();
|
updateWorkspacePadding();
|
||||||
|
|
||||||
|
@ -239,12 +239,21 @@ public class DeviceProfile {
|
||||||
IconShape.DEFAULT_PATH_SIZE);
|
IconShape.DEFAULT_PATH_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceProfile copy(Context context) {
|
public Builder toBuilder(Context context) {
|
||||||
Point size = new Point(availableWidthPx, availableHeightPx);
|
Point size = new Point(availableWidthPx, availableHeightPx);
|
||||||
return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
|
return new Builder(context, inv, mInfo)
|
||||||
isMultiWindowMode);
|
.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) {
|
public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
|
||||||
// We take the minimum sizes of this profile and it's multi-window variant to ensure that
|
// We take the minimum sizes of this profile and it's multi-window variant to ensure that
|
||||||
// the system decor is always excluded.
|
// the system decor is always excluded.
|
||||||
|
@ -253,8 +262,11 @@ public class DeviceProfile {
|
||||||
// In multi-window mode, we can have widthPx = availableWidthPx
|
// In multi-window mode, we can have widthPx = availableWidthPx
|
||||||
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
|
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
|
||||||
// widthPx and heightPx values where it's needed.
|
// widthPx and heightPx values where it's needed.
|
||||||
DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
|
DeviceProfile profile = toBuilder(context)
|
||||||
isLandscape, true);
|
.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.
|
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
|
||||||
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
|
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
|
||||||
|
@ -289,27 +301,30 @@ public class DeviceProfile {
|
||||||
iconTextSizePx = 0;
|
iconTextSizePx = 0;
|
||||||
iconDrawablePaddingPx = 0;
|
iconDrawablePaddingPx = 0;
|
||||||
cellHeightPx = iconSizePx;
|
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
|
* Re-computes the all-apps cell size to be independent of workspace
|
||||||
// All Apps cell height.
|
*/
|
||||||
|
public void autoResizeAllAppsCells() {
|
||||||
int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
|
int topBottomPadding = allAppsIconDrawablePaddingPx * (isVerticalBarLayout() ? 2 : 1);
|
||||||
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
|
allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
|
||||||
+ Utilities.calculateTextHeight(allAppsIconTextSizePx)
|
+ Utilities.calculateTextHeight(allAppsIconTextSizePx)
|
||||||
+ topBottomPadding * 2;
|
+ topBottomPadding * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAvailableDimensions(DisplayMetrics dm, Resources res) {
|
private void updateAvailableDimensions(Resources res) {
|
||||||
updateIconSize(1f, res, dm);
|
updateIconSize(1f, res);
|
||||||
|
|
||||||
// Check to see if the icons fit within the available height. If not, then scale down.
|
// Check to see if the icons fit within the available height. If not, then scale down.
|
||||||
float usedHeight = (cellHeightPx * inv.numRows);
|
float usedHeight = (cellHeightPx * inv.numRows);
|
||||||
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
|
int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
|
||||||
if (usedHeight > maxHeight) {
|
if (usedHeight > maxHeight) {
|
||||||
float scale = maxHeight / usedHeight;
|
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,
|
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
|
||||||
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
|
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
|
||||||
*/
|
*/
|
||||||
private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
|
private void updateIconSize(float scale, Resources res) {
|
||||||
// Workspace
|
// Workspace
|
||||||
final boolean isVerticalLayout = isVerticalBarLayout();
|
final boolean isVerticalLayout = isVerticalBarLayout();
|
||||||
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
|
float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
|
||||||
iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
|
iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, mInfo.metrics)
|
||||||
iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
|
* scale));
|
||||||
|
iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
|
||||||
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
|
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
|
||||||
|
|
||||||
cellHeightPx = iconSizePx + iconDrawablePaddingPx
|
cellHeightPx = iconSizePx + iconDrawablePaddingPx
|
||||||
|
@ -340,8 +356,8 @@ public class DeviceProfile {
|
||||||
|
|
||||||
// All apps
|
// All apps
|
||||||
if (allAppsHasDifferentNumColumns()) {
|
if (allAppsHasDifferentNumColumns()) {
|
||||||
allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm);
|
allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
|
||||||
allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm);
|
allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
|
||||||
allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
|
allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
|
||||||
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
|
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
|
||||||
} else {
|
} else {
|
||||||
|
@ -381,12 +397,12 @@ public class DeviceProfile {
|
||||||
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
|
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)
|
int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top)
|
||||||
+ res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
|
+ res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom)
|
||||||
+ Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size));
|
+ 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.
|
// Don't let the folder get too close to the edges of the screen.
|
||||||
int folderMargin = edgeMarginPx * 2;
|
int folderMargin = edgeMarginPx * 2;
|
||||||
|
@ -405,12 +421,12 @@ public class DeviceProfile {
|
||||||
|
|
||||||
float scale = Math.min(scaleX, scaleY);
|
float scale = Math.min(scaleX, scaleY);
|
||||||
if (scale < 1f) {
|
if (scale < 1f) {
|
||||||
updateFolderCellSize(scale, dm, res);
|
updateFolderCellSize(scale, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res) {
|
private void updateFolderCellSize(float scale, Resources res) {
|
||||||
folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, dm) * scale);
|
folderChildIconSizePx = (int) (ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) * scale);
|
||||||
folderChildTextSizePx =
|
folderChildTextSizePx =
|
||||||
(int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
|
(int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale);
|
||||||
|
|
||||||
|
@ -627,4 +643,55 @@ public class DeviceProfile {
|
||||||
*/
|
*/
|
||||||
void onDeviceProfileChanged(DeviceProfile dp);
|
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.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
@ -159,10 +160,12 @@ public class InvariantDeviceProfile {
|
||||||
|
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private InvariantDeviceProfile(Context context) {
|
private InvariantDeviceProfile(Context context) {
|
||||||
String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
|
String gridName = getCurrentGridName(context);
|
||||||
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
|
String newGridName = initGrid(context, gridName);
|
||||||
: null;
|
if (!newGridName.equals(gridName)) {
|
||||||
initGrid(context, gridName);
|
Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
|
||||||
|
}
|
||||||
|
|
||||||
mConfigMonitor = new ConfigMonitor(context,
|
mConfigMonitor = new ConfigMonitor(context,
|
||||||
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
|
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
|
||||||
mOverlayMonitor = new OverlayMonitor(context);
|
mOverlayMonitor = new OverlayMonitor(context);
|
||||||
|
@ -182,13 +185,32 @@ public class InvariantDeviceProfile {
|
||||||
* This constructor should NOT have any monitors by design.
|
* This constructor should NOT have any monitors by design.
|
||||||
*/
|
*/
|
||||||
public InvariantDeviceProfile(Context context, Display display) {
|
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) {
|
public static String getCurrentGridName(Context context) {
|
||||||
return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
|
SharedPreferences prefs = Utilities.getPrefs(context);
|
||||||
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
|
return prefs.getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
|
||||||
: null;
|
? prefs.getString(KEY_IDP_GRID_NAME, null) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,27 +225,17 @@ public class InvariantDeviceProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String initGrid(Context context, String gridName) {
|
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) {
|
private void initGrid(
|
||||||
Point smallestSize = new Point(displayInfo.smallestSize);
|
Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
|
||||||
Point largestSize = new Point(displayInfo.largestSize);
|
GridOption closestProfile = displayOption.grid;
|
||||||
|
|
||||||
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;
|
|
||||||
numRows = closestProfile.numRows;
|
numRows = closestProfile.numRows;
|
||||||
numColumns = closestProfile.numColumns;
|
numColumns = closestProfile.numColumns;
|
||||||
numHotseatIcons = closestProfile.numHotseatIcons;
|
numHotseatIcons = closestProfile.numHotseatIcons;
|
||||||
|
@ -236,21 +248,16 @@ public class InvariantDeviceProfile {
|
||||||
|
|
||||||
mExtraAttrs = closestProfile.extraAttrs;
|
mExtraAttrs = closestProfile.extraAttrs;
|
||||||
|
|
||||||
if (!closestProfile.name.equals(gridName)) {
|
iconSize = displayOption.iconSize;
|
||||||
Utilities.getPrefs(context).edit()
|
|
||||||
.putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
iconSize = interpolatedDisplayOption.iconSize;
|
|
||||||
iconShapePath = getIconShapePath(context);
|
iconShapePath = getIconShapePath(context);
|
||||||
landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
|
landscapeIconSize = displayOption.landscapeIconSize;
|
||||||
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
|
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
|
||||||
iconTextSize = interpolatedDisplayOption.iconTextSize;
|
iconTextSize = displayOption.iconTextSize;
|
||||||
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
|
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
|
||||||
|
|
||||||
if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
|
if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
|
||||||
allAppsIconSize = interpolatedDisplayOption.allAppsIconSize;
|
allAppsIconSize = displayOption.allAppsIconSize;
|
||||||
allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize;
|
allAppsIconTextSize = displayOption.allAppsIconTextSize;
|
||||||
} else {
|
} else {
|
||||||
allAppsIconSize = iconSize;
|
allAppsIconSize = iconSize;
|
||||||
allAppsIconTextSize = iconTextSize;
|
allAppsIconTextSize = iconTextSize;
|
||||||
|
@ -266,10 +273,12 @@ public class InvariantDeviceProfile {
|
||||||
int smallSide = Math.min(realSize.x, realSize.y);
|
int smallSide = Math.min(realSize.x, realSize.y);
|
||||||
int largeSide = Math.max(realSize.x, realSize.y);
|
int largeSide = Math.max(realSize.x, realSize.y);
|
||||||
|
|
||||||
landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
|
DeviceProfile.Builder builder = new DeviceProfile.Builder(context, this, displayInfo)
|
||||||
largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
|
.setSizeRange(new Point(displayInfo.smallestSize),
|
||||||
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
|
new Point(displayInfo.largestSize));
|
||||||
smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
|
|
||||||
|
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
|
// We need to ensure that there is enough extra space in the wallpaper
|
||||||
// for the intended parallax effects
|
// for the intended parallax effects
|
||||||
|
@ -283,8 +292,6 @@ public class InvariantDeviceProfile {
|
||||||
|
|
||||||
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
|
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
|
||||||
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
|
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
|
||||||
|
|
||||||
return closestProfile.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -453,6 +460,41 @@ public class InvariantDeviceProfile {
|
||||||
return (float) Math.hypot(x1 - x0, y1 - y0);
|
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
|
@VisibleForTesting
|
||||||
static DisplayOption invDistWeightedInterpolate(float width, float height,
|
static DisplayOption invDistWeightedInterpolate(float width, float height,
|
||||||
ArrayList<DisplayOption> points) {
|
ArrayList<DisplayOption> points) {
|
||||||
|
@ -573,7 +615,6 @@ public class InvariantDeviceProfile {
|
||||||
private static final class DisplayOption {
|
private static final class DisplayOption {
|
||||||
private final GridOption grid;
|
private final GridOption grid;
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final float minWidthDps;
|
private final float minWidthDps;
|
||||||
private final float minHeightDps;
|
private final float minHeightDps;
|
||||||
private final boolean canBeDefault;
|
private final boolean canBeDefault;
|
||||||
|
@ -590,7 +631,6 @@ public class InvariantDeviceProfile {
|
||||||
TypedArray a = context.obtainStyledAttributes(
|
TypedArray a = context.obtainStyledAttributes(
|
||||||
attrs, R.styleable.ProfileDisplayOption);
|
attrs, R.styleable.ProfileDisplayOption);
|
||||||
|
|
||||||
name = a.getString(R.styleable.ProfileDisplayOption_name);
|
|
||||||
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
|
minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
|
||||||
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
|
minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
|
||||||
canBeDefault = a.getBoolean(
|
canBeDefault = a.getBoolean(
|
||||||
|
@ -609,8 +649,11 @@ public class InvariantDeviceProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayOption() {
|
DisplayOption() {
|
||||||
grid = null;
|
this(null);
|
||||||
name = null;
|
}
|
||||||
|
|
||||||
|
DisplayOption(GridOption grid) {
|
||||||
|
this.grid = grid;
|
||||||
minWidthDps = 0;
|
minWidthDps = 0;
|
||||||
minHeightDps = 0;
|
minHeightDps = 0;
|
||||||
canBeDefault = false;
|
canBeDefault = false;
|
||||||
|
|
|
@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
|
||||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
@ -30,7 +31,6 @@ import android.view.ViewConfiguration;
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
|
|
||||||
import com.android.launcher3.Insettable;
|
import com.android.launcher3.Insettable;
|
||||||
import com.android.launcher3.Launcher;
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
import com.android.launcher3.pm.UserCache;
|
import com.android.launcher3.pm.UserCache;
|
||||||
|
@ -79,9 +79,8 @@ public class WorkModeSwitch extends Switch implements Insettable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void toggle() {
|
public void toggle() {
|
||||||
Launcher launcher = Launcher.getLauncher(getContext());
|
|
||||||
// don't show tip if user uses toggle
|
// 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());
|
trySetQuietModeEnabledToAllProfilesAsync(isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,9 +202,8 @@ public class WorkModeSwitch extends Switch implements Insettable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldShowWorkSwitch() {
|
private boolean shouldShowWorkSwitch() {
|
||||||
Launcher launcher = Launcher.getLauncher(getContext());
|
return Utilities.ATLEAST_P && (hasShortcutsPermission(getContext())
|
||||||
return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
|
|| getContext().checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
|
||||||
|| launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
|
|
||||||
== PackageManager.PERMISSION_GRANTED);
|
== PackageManager.PERMISSION_GRANTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,12 +211,14 @@ public class WorkModeSwitch extends Switch implements Insettable {
|
||||||
* Shows a work tip on the Nth work tab open
|
* Shows a work tip on the Nth work tab open
|
||||||
*/
|
*/
|
||||||
public void showTipifNeeded() {
|
public void showTipifNeeded() {
|
||||||
Launcher launcher = Launcher.getLauncher(getContext());
|
Context context = getContext();
|
||||||
int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
|
SharedPreferences prefs = Utilities.getPrefs(context);
|
||||||
|
int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
|
||||||
if (tipCounter < 0) return;
|
if (tipCounter < 0) return;
|
||||||
if (tipCounter == 0) {
|
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) {
|
if (mDragLayer != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
|
|
||||||
InvariantDeviceProfile currentDisplayIdp =
|
InvariantDeviceProfile currentDisplayIdp =
|
||||||
new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
|
new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
|
||||||
|
|
||||||
// Pick the device profile with the smaller icon size so that the cached icons are
|
// Disable transpose layout and use multi-window mode so that the icons are scaled properly
|
||||||
// shown properly
|
mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
|
||||||
if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
|
.toBuilder(this)
|
||||||
mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
|
.setMultiWindowMode(true)
|
||||||
} else {
|
.setTransposeLayoutWithOrientation(false)
|
||||||
mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
|
.build();
|
||||||
}
|
mDeviceProfile.autoResizeAllAppsCells();
|
||||||
|
|
||||||
setContentView(R.layout.secondary_launcher);
|
setContentView(R.layout.secondary_launcher);
|
||||||
mDragLayer = findViewById(R.id.drag_layer);
|
mDragLayer = findViewById(R.id.drag_layer);
|
||||||
|
|
|
@ -117,10 +117,12 @@ public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher>
|
||||||
if (child == mAppsView) {
|
if (child == mAppsView) {
|
||||||
int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
|
int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
|
||||||
+ grid.cellLayoutPaddingLeftRightPx);
|
+ grid.cellLayoutPaddingLeftRightPx);
|
||||||
int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
|
|
||||||
|
|
||||||
|
int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
|
||||||
int appsWidth = Math.min(width, maxWidth);
|
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(
|
mAppsView.measure(
|
||||||
makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
|
makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
|
||||||
|
|
|
@ -32,7 +32,7 @@ import android.widget.TextView;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.android.launcher3.AbstractFloatingView;
|
import com.android.launcher3.AbstractFloatingView;
|
||||||
import com.android.launcher3.Launcher;
|
import com.android.launcher3.BaseDraggingActivity;
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
import com.android.launcher3.anim.Interpolators;
|
import com.android.launcher3.anim.Interpolators;
|
||||||
import com.android.launcher3.dragndrop.DragLayer;
|
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 SHOW_DURATION_MS = 300;
|
||||||
private static final long HIDE_DURATION_MS = 100;
|
private static final long HIDE_DURATION_MS = 100;
|
||||||
|
|
||||||
protected final Launcher mLauncher;
|
protected final BaseDraggingActivity mActivity;
|
||||||
private final Handler mHandler = new Handler();
|
private final Handler mHandler = new Handler();
|
||||||
private Runnable mOnClosed;
|
private Runnable mOnClosed;
|
||||||
|
|
||||||
public ArrowTipView(Context context) {
|
public ArrowTipView(Context context) {
|
||||||
super(context, null, 0);
|
super(context, null, 0);
|
||||||
mLauncher = Launcher.getLauncher(context);
|
mActivity = BaseDraggingActivity.fromContext(context);
|
||||||
init(context);
|
init(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +75,11 @@ public class ArrowTipView extends AbstractFloatingView {
|
||||||
.setStartDelay(0)
|
.setStartDelay(0)
|
||||||
.setDuration(HIDE_DURATION_MS)
|
.setDuration(HIDE_DURATION_MS)
|
||||||
.setInterpolator(Interpolators.ACCEL)
|
.setInterpolator(Interpolators.ACCEL)
|
||||||
.withEndAction(() -> mLauncher.getDragLayer().removeView(this))
|
.withEndAction(() -> mActivity.getDragLayer().removeView(this))
|
||||||
.start();
|
.start();
|
||||||
} else {
|
} else {
|
||||||
animate().cancel();
|
animate().cancel();
|
||||||
mLauncher.getDragLayer().removeView(this);
|
mActivity.getDragLayer().removeView(this);
|
||||||
}
|
}
|
||||||
if (mOnClosed != null) mOnClosed.run();
|
if (mOnClosed != null) mOnClosed.run();
|
||||||
mIsOpen = false;
|
mIsOpen = false;
|
||||||
|
@ -126,12 +126,12 @@ public class ArrowTipView extends AbstractFloatingView {
|
||||||
*/
|
*/
|
||||||
public ArrowTipView show(String text, int top) {
|
public ArrowTipView show(String text, int top) {
|
||||||
((TextView) findViewById(R.id.text)).setText(text);
|
((TextView) findViewById(R.id.text)).setText(text);
|
||||||
mLauncher.getDragLayer().addView(this);
|
mActivity.getDragLayer().addView(this);
|
||||||
|
|
||||||
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
|
DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
|
||||||
params.gravity = Gravity.CENTER_HORIZONTAL;
|
params.gravity = Gravity.CENTER_HORIZONTAL;
|
||||||
params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
|
params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left;
|
||||||
params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
|
params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right;
|
||||||
post(() -> setY(top - getHeight()));
|
post(() -> setY(top - getHeight()));
|
||||||
setAlpha(0);
|
setAlpha(0);
|
||||||
animate()
|
animate()
|
||||||
|
|
Loading…
Reference in New Issue