Snap for 9728260 from 9abfa85900 to udc-release
Change-Id: I5acb4b7d22c0ab8c2fce06d8cfeecf51420eb084
This commit is contained in:
@@ -221,6 +221,12 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(
|
||||
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
|
||||
useTestWorkspaceLayout(null);
|
||||
return response;
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
|
||||
line="72"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
|
||||
line="73"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.graphics.Outline#setPath`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
|
||||
line="114"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.View#getWindowInsetsController`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="902"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.WindowInsets.Type#ime`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="902"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.WindowInsetsController#hide`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="902"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#resetColorResources`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java"
|
||||
line="114"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 31 (current min is 30): `android.appwidget.AppWidgetHostView#setColorResources`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
|
||||
line="415"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
|
||||
line="93"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1814"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1824"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1815"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1823"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Method reference requires API level 28 (current min is 26): `Person::getKey`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
|
||||
line="195"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Method reference requires API level 28 (current min is 26): `Person::getKey`">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
|
||||
line="73"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
|
||||
line="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
|
||||
line="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
|
||||
line="20"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<!-- Applies to large tablet screens portrait -->
|
||||
<resources>
|
||||
<!-- Taskbar -->
|
||||
<!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
|
||||
<bool name="start_align_taskbar">true</bool>
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<!-- Applies to large tablet screens landscape -->
|
||||
<resources>
|
||||
<!-- Taskbar -->
|
||||
<!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
|
||||
<bool name="start_align_taskbar">false</bool>
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<!-- Applies to large tablet screens portrait -->
|
||||
<resources>
|
||||
<!-- Taskbar -->
|
||||
<!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
|
||||
<bool name="start_align_taskbar">true</bool>
|
||||
</resources>
|
||||
@@ -51,4 +51,8 @@
|
||||
</item>
|
||||
|
||||
<string name="setup_wizard_pkg" translatable="false" />
|
||||
|
||||
<!-- Taskbar -->
|
||||
<!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
|
||||
<bool name="start_align_taskbar">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.HapticFeedbackConstants.LONG_PRESS;
|
||||
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
|
||||
@@ -43,6 +44,7 @@ import android.util.Log;
|
||||
import android.view.InsetsController;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -189,6 +191,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
// Stashed handle properties.
|
||||
private MultiProperty mTaskbarStashedHandleAlpha;
|
||||
private AnimatedFloat mTaskbarStashedHandleHintScale;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
/** Whether we are currently visually stashed (might change based on launcher state). */
|
||||
private boolean mIsStashed = false;
|
||||
@@ -221,6 +224,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mActivity = activity;
|
||||
mPrefs = LauncherPrefs.getPrefs(mActivity);
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
|
||||
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
|
||||
if (isPhoneMode()) {
|
||||
// DeviceProfile's taskbar vars aren't initialized w/ the flag off
|
||||
Resources resources = mActivity.getResources();
|
||||
@@ -1018,7 +1022,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
cancelTimeoutIfExists();
|
||||
|
||||
mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout);
|
||||
mTimeoutAlarm.setAlarm(NO_TOUCH_TIMEOUT_TO_STASH_MS);
|
||||
mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
|
||||
*/
|
||||
private long getTaskbarAutoHideTimeout() {
|
||||
return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
|
||||
FLAG_CONTENT_CONTROLS);
|
||||
}
|
||||
|
||||
private void onTaskbarTimeout(Alarm alarm) {
|
||||
|
||||
@@ -94,6 +94,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
private float mTransientTaskbarAllAppsButtonTranslationXOffset;
|
||||
|
||||
private final boolean mStartAlignTaskbar;
|
||||
|
||||
public TaskbarView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -121,6 +123,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
resources.getDimension(isTransientTaskbar
|
||||
? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
|
||||
: R.dimen.taskbar_all_apps_button_translation_x_offset);
|
||||
mStartAlignTaskbar = mActivityContext.isThreeButtonNav()
|
||||
&& resources.getBoolean(R.bool.start_align_taskbar);
|
||||
|
||||
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
|
||||
int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
|
||||
@@ -372,10 +376,22 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
boolean needMoreSpaceForNav = layoutRtl ?
|
||||
navSpaceNeeded > (iconEnd - spaceNeeded) :
|
||||
iconEnd > (right - navSpaceNeeded);
|
||||
if (needMoreSpaceForNav) {
|
||||
int offset = layoutRtl ?
|
||||
navSpaceNeeded - (iconEnd - spaceNeeded) :
|
||||
(right - navSpaceNeeded) - iconEnd;
|
||||
|
||||
if (mStartAlignTaskbar) {
|
||||
// Taskbar is aligned to the start
|
||||
int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
|
||||
|
||||
if (layoutRtl) {
|
||||
iconEnd = right - startSpacingPx;
|
||||
} else {
|
||||
iconEnd = startSpacingPx + spaceNeeded;
|
||||
}
|
||||
} else if (needMoreSpaceForNav) {
|
||||
// Add offset to account for nav bar when taskbar is centered
|
||||
int offset = layoutRtl
|
||||
? navSpaceNeeded - (iconEnd - spaceNeeded)
|
||||
: (right - navSpaceNeeded) - iconEnd;
|
||||
|
||||
iconEnd += offset;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
float maxScale = res.getFloat(R.dimen.overview_max_scale);
|
||||
int taskMargin = dp.overviewTaskMarginPx;
|
||||
calculateTaskSizeInternal(
|
||||
context,
|
||||
dp,
|
||||
dp.overviewTaskThumbnailTopMarginPx,
|
||||
dp.getOverviewActionsClaimedSpace(),
|
||||
@@ -259,10 +260,10 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
float maxScale = res.getFloat(R.dimen.overview_max_scale);
|
||||
Rect gridRect = new Rect();
|
||||
calculateGridSize(dp, gridRect);
|
||||
calculateTaskSizeInternal(dp, gridRect, maxScale, Gravity.CENTER, outRect);
|
||||
calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
|
||||
}
|
||||
|
||||
private void calculateTaskSizeInternal(DeviceProfile dp, int claimedSpaceAbove,
|
||||
private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
|
||||
int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
|
||||
Rect outRect) {
|
||||
Rect insets = dp.getInsets();
|
||||
@@ -275,12 +276,12 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
minimumHorizontalPadding,
|
||||
claimedSpaceBelow);
|
||||
|
||||
calculateTaskSizeInternal(dp, potentialTaskRect, maxScale, gravity, outRect);
|
||||
calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
|
||||
}
|
||||
|
||||
private void calculateTaskSizeInternal(DeviceProfile dp,
|
||||
private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
|
||||
Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
|
||||
PointF taskDimension = getTaskDimension(dp);
|
||||
PointF taskDimension = getTaskDimension(context, dp);
|
||||
|
||||
float scale = Math.min(
|
||||
potentialTaskRect.width() / taskDimension.x,
|
||||
@@ -292,19 +293,19 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
|
||||
}
|
||||
|
||||
private static PointF getTaskDimension(DeviceProfile dp) {
|
||||
private static PointF getTaskDimension(Context context, DeviceProfile dp) {
|
||||
PointF dimension = new PointF();
|
||||
getTaskDimension(dp, dimension);
|
||||
getTaskDimension(context, dp, dimension);
|
||||
return dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dimension of the task in the current system state.
|
||||
*/
|
||||
public static void getTaskDimension(DeviceProfile dp, PointF out) {
|
||||
public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
|
||||
out.x = dp.widthPx;
|
||||
out.y = dp.heightPx;
|
||||
if (dp.isTablet) {
|
||||
if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) {
|
||||
out.y -= dp.taskbarSize;
|
||||
}
|
||||
}
|
||||
@@ -339,7 +340,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx
|
||||
- dp.overviewRowSpacing) / 2f;
|
||||
|
||||
PointF taskDimension = getTaskDimension(dp);
|
||||
PointF taskDimension = getTaskDimension(context, dp);
|
||||
float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
|
||||
int outWidth = Math.round(scale * taskDimension.x);
|
||||
int outHeight = Math.round(scale * taskDimension.y);
|
||||
@@ -373,6 +374,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
|
||||
}
|
||||
calculateTaskSizeInternal(
|
||||
context,
|
||||
dp,
|
||||
dp.overviewTaskMarginPx,
|
||||
claimedSpaceBelow,
|
||||
|
||||
@@ -396,7 +396,7 @@ public class RecentsOrientedState implements
|
||||
* Returns the scale and pivot so that the provided taskRect can fit the provided full size
|
||||
*/
|
||||
public float getFullScreenScaleAndPivot(Rect taskView, DeviceProfile dp, PointF outPivot) {
|
||||
getTaskDimension(dp, outPivot);
|
||||
getTaskDimension(mContext, dp, outPivot);
|
||||
float scale = Math.min(outPivot.x / taskView.width(), outPivot.y / taskView.height());
|
||||
if (scale == 1) {
|
||||
outPivot.set(taskView.centerX(), taskView.centerY());
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.quickstep.util
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.anim.PendingAnimation
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
|
||||
import com.android.quickstep.views.TaskThumbnailView
|
||||
import com.android.quickstep.views.TaskView
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* Utils class to help run animations for initiating split screen from launcher.
|
||||
* Will be expanded with future refactors. Works in conjunction with the state stored in
|
||||
* [SplitSelectStateController]
|
||||
*/
|
||||
class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
|
||||
companion object {
|
||||
// Break this out into maybe enums? Abstractions into its own classes? Tbd.
|
||||
data class SplitAnimInitProps(
|
||||
val originalView: View,
|
||||
val originalBitmap: Bitmap?,
|
||||
val iconDrawable: Drawable,
|
||||
val fadeWithThumbnail: Boolean,
|
||||
val isStagedTask: Boolean,
|
||||
val iconView: View?
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns different elements to animate for the initial split selection animation
|
||||
* depending on the state of the surface from which the split was initiated
|
||||
*/
|
||||
fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
|
||||
splitSelectSourceSupplier: Supplier<SplitSelectSource>)
|
||||
: SplitAnimInitProps {
|
||||
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
|
||||
// Initiating from home
|
||||
val splitSelectSource = splitSelectSourceSupplier.get()
|
||||
return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null,
|
||||
splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
|
||||
iconView = null)
|
||||
} else if (splitSelectStateController.isDismissingFromSplitPair) {
|
||||
// Initiating split from overview, but on a split pair
|
||||
val taskView = taskViewSupplier.get()
|
||||
for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
|
||||
if (container.task.key.id == splitSelectStateController.initialTaskId) {
|
||||
return SplitAnimInitProps(container.thumbnailView,
|
||||
container.thumbnailView.thumbnail, container.iconView.drawable!!,
|
||||
fadeWithThumbnail = true, isStagedTask = true,
|
||||
iconView = container.iconView
|
||||
)
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("Attempting to init split from existing split pair " +
|
||||
"without a valid taskIdAttributeContainer")
|
||||
} else {
|
||||
// Initiating split from overview on fullscreen task TaskView
|
||||
val taskView = taskViewSupplier.get()
|
||||
return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
|
||||
taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true,
|
||||
taskView.iconView
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When selecting first app from split pair, second app's thumbnail remains. This animates
|
||||
* the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
|
||||
* it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
|
||||
* Note: The app that **was not** selected as the first split app should be the container that's
|
||||
* passed through.
|
||||
*
|
||||
* @param builder Adds animation to this
|
||||
* @param taskIdAttributeContainer container of the app that **was not** selected
|
||||
* @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
|
||||
* (opposite of that representing [taskIdAttributeContainer])
|
||||
*/
|
||||
fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
|
||||
builder: PendingAnimation, deviceProfile: DeviceProfile,
|
||||
taskViewWidth: Int, taskViewHeight: Int,
|
||||
isPrimaryTaskSplitting: Boolean) {
|
||||
val thumbnail = taskIdAttributeContainer.thumbnailView
|
||||
val iconView: View = taskIdAttributeContainer.iconView
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
|
||||
thumbnail.setShowSplashForSplitSelection(true)
|
||||
if (deviceProfile.isLandscape) {
|
||||
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
|
||||
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
|
||||
val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
|
||||
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail,
|
||||
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
|
||||
// icons are anchored from Gravity.END, so need to use negative translation
|
||||
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
|
||||
-centerIconTranslationX))
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
|
||||
|
||||
// Reset other dimensions
|
||||
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
|
||||
thumbnail.scaleY = 1f
|
||||
val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
|
||||
deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail,
|
||||
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
|
||||
translateYResetVal))
|
||||
} else {
|
||||
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
|
||||
// primary thumbnail has layout margin above it, so secondary thumbnail needs to take
|
||||
// that into account. We should migrate to only using translations otherwise this
|
||||
// asymmetry causes problems..
|
||||
|
||||
// Icon defaults to center | horizontal, we add additional translation for split
|
||||
val centerIconTranslationX = 0f
|
||||
var centerThumbnailTranslationY: Float
|
||||
|
||||
// TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
|
||||
// thumbnail needs to take that into account. We should migrate to only using
|
||||
// translations otherwise this asymmetry causes problems..
|
||||
if (isPrimaryTaskSplitting) {
|
||||
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
|
||||
centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
.toFloat()
|
||||
} else {
|
||||
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
|
||||
}
|
||||
val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail,
|
||||
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
|
||||
|
||||
// icons are anchored from Gravity.END, so need to use negative translation
|
||||
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
|
||||
centerIconTranslationX))
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
|
||||
|
||||
// Reset other dimensions
|
||||
thumbnail.scaleX = 1f
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail,
|
||||
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ public class SplitSelectStateController {
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final RecentsModel mRecentTasksModel;
|
||||
private final SplitAnimationController mSplitAnimationController;
|
||||
private StatsLogManager mStatsLogManager;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final StateManager mStateManager;
|
||||
@@ -97,6 +98,11 @@ public class SplitSelectStateController {
|
||||
private boolean mRecentsAnimationRunning;
|
||||
/** If {@code true}, animates the existing task view split placeholder view */
|
||||
private boolean mAnimateCurrentTaskDismissal;
|
||||
/**
|
||||
* Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
|
||||
* split pair task view without wanting to animate current task dismissal overall
|
||||
*/
|
||||
private boolean mDismissingFromSplitPair;
|
||||
@Nullable
|
||||
private UserHandle mUser;
|
||||
/** If not null, this is the TaskView we want to launch from */
|
||||
@@ -117,6 +123,7 @@ public class SplitSelectStateController {
|
||||
mStateManager = stateManager;
|
||||
mDepthController = depthController;
|
||||
mRecentTasksModel = recentsModel;
|
||||
mSplitAnimationController = new SplitAnimationController(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,6 +408,18 @@ public class SplitSelectStateController {
|
||||
mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
|
||||
}
|
||||
|
||||
public boolean isDismissingFromSplitPair() {
|
||||
return mDismissingFromSplitPair;
|
||||
}
|
||||
|
||||
public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
|
||||
mDismissingFromSplitPair = dismissingFromSplitPair;
|
||||
}
|
||||
|
||||
public SplitAnimationController getSplitAnimationController() {
|
||||
return mSplitAnimationController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires Shell Transitions
|
||||
*/
|
||||
@@ -508,6 +527,7 @@ public class SplitSelectStateController {
|
||||
mItemInfo = null;
|
||||
mSplitEvent = null;
|
||||
mAnimateCurrentTaskDismissal = false;
|
||||
mDismissingFromSplitPair = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,6 +554,10 @@ public class SplitSelectStateController {
|
||||
return mInitialTaskId;
|
||||
}
|
||||
|
||||
public int getSecondTaskId() {
|
||||
return mSecondTaskId;
|
||||
}
|
||||
|
||||
private boolean isSecondTaskIntentSet() {
|
||||
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
|
||||
}
|
||||
|
||||
@@ -272,9 +272,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
*/
|
||||
public RectF getCurrentCropRect() {
|
||||
// Crop rect is the inverse of thumbnail matrix
|
||||
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
|
||||
mTempRectF.set(-insets.left, -insets.top,
|
||||
mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
|
||||
mTempRectF.set(0, 0, mTaskRect.width(), mTaskRect.height());
|
||||
mInversePositionMatrix.mapRect(mTempRectF);
|
||||
return mTempRectF;
|
||||
}
|
||||
@@ -351,14 +349,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
/* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
|
||||
|
||||
// Apply thumbnail matrix
|
||||
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
|
||||
float scale = mCurrentFullscreenParams.mScale;
|
||||
float taskWidth = mTaskRect.width();
|
||||
float taskHeight = mTaskRect.height();
|
||||
|
||||
mMatrix.set(mPositionHelper.getMatrix());
|
||||
mMatrix.postTranslate(insets.left, insets.top);
|
||||
mMatrix.postScale(scale, scale);
|
||||
|
||||
// Apply TaskView matrix: taskRect, translate
|
||||
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
|
||||
@@ -378,8 +372,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
applyWindowToHomeRotation(mMatrix);
|
||||
|
||||
// Crop rect is the inverse of thumbnail matrix
|
||||
mTempRectF.set(-insets.left, -insets.top,
|
||||
taskWidth + insets.right, taskHeight + insets.bottom);
|
||||
mTempRectF.set(0, 0, taskWidth, taskHeight);
|
||||
mInversePositionMatrix.mapRect(mTempRectF);
|
||||
mTempRectF.roundOut(mTmpCropRect);
|
||||
|
||||
@@ -389,7 +382,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "progress: " + fullScreenProgress
|
||||
+ " scale: " + scale
|
||||
+ " recentsViewScale: " + recentsViewScale.value
|
||||
+ " crop: " + mTmpCropRect
|
||||
+ " radius: " + getCurrentCornerRadius()
|
||||
|
||||
@@ -496,7 +496,7 @@ public class DesktopTaskView extends TaskView {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setThumbnailVisibility(int visibility) {
|
||||
void setThumbnailVisibility(int visibility, int taskId) {
|
||||
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
|
||||
mSnapshotViewMap.valueAt(i).setVisibility(visibility);
|
||||
}
|
||||
|
||||
@@ -97,11 +97,6 @@ public final class DigitalWellBeingToast {
|
||||
private View mBanner;
|
||||
private ViewOutlineProvider mOldBannerOutlineProvider;
|
||||
private float mBannerOffsetPercentage;
|
||||
/**
|
||||
* Clips rect provided by {@link #mOldBannerOutlineProvider} when in the model state to
|
||||
* hide this banner as the taskView scales up and down
|
||||
*/
|
||||
private float mModalOffset = 0f;
|
||||
@Nullable
|
||||
private SplitBounds mSplitBounds;
|
||||
private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
|
||||
@@ -336,17 +331,15 @@ public final class DigitalWellBeingToast {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
mOldBannerOutlineProvider.getOutline(view, outline);
|
||||
float verticalTranslation = -view.getTranslationY() + mModalOffset
|
||||
+ mSplitOffsetTranslationY;
|
||||
float verticalTranslation = -view.getTranslationY() + mSplitOffsetTranslationY;
|
||||
outline.offset(0, Math.round(verticalTranslation));
|
||||
}
|
||||
});
|
||||
mBanner.setClipToOutline(true);
|
||||
}
|
||||
|
||||
void updateBannerOffset(float offsetPercentage, float verticalOffset) {
|
||||
void updateBannerOffset(float offsetPercentage) {
|
||||
if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
|
||||
mModalOffset = verticalOffset;
|
||||
mBannerOffsetPercentage = offsetPercentage;
|
||||
updateTranslationY();
|
||||
mBanner.invalidateOutline();
|
||||
@@ -359,10 +352,7 @@ public final class DigitalWellBeingToast {
|
||||
}
|
||||
|
||||
mBanner.setTranslationY(
|
||||
(mBannerOffsetPercentage * mBanner.getHeight()) +
|
||||
mModalOffset +
|
||||
mSplitOffsetTranslationY
|
||||
);
|
||||
(mBannerOffsetPercentage * mBanner.getHeight()) + mSplitOffsetTranslationY);
|
||||
}
|
||||
|
||||
private void updateTranslationX() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
|
||||
@@ -25,6 +27,7 @@ import com.android.quickstep.TaskIconCache;
|
||||
import com.android.quickstep.TaskThumbnailCache;
|
||||
import com.android.quickstep.util.CancellableTask;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
@@ -267,6 +270,19 @@ public class GroupedTaskView extends TaskView {
|
||||
|
||||
@Override
|
||||
protected int getLastSelectedChildTaskIndex() {
|
||||
SplitSelectStateController splitSelectController =
|
||||
getRecentsView().getSplitSelectController();
|
||||
if (splitSelectController.isDismissingFromSplitPair()) {
|
||||
// return the container index of the task that wasn't initially selected to split with
|
||||
// because that is the only remaining app that can be selected. The coordinate checks
|
||||
// below aren't reliable since both of those views may be gone/transformed
|
||||
int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
|
||||
if (initSplitTaskId != INVALID_TASK_ID) {
|
||||
return initSplitTaskId == mTask.key.id ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check which of the two apps was selected
|
||||
if (isCoordInView(mIconView2, mLastTouchDownPosition)
|
||||
|| isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
|
||||
return 1;
|
||||
@@ -296,9 +312,30 @@ public class GroupedTaskView extends TaskView {
|
||||
if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
|
||||
return;
|
||||
}
|
||||
getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
|
||||
mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
|
||||
mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
|
||||
int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
|
||||
if (initSplitTaskId == INVALID_TASK_ID) {
|
||||
getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
|
||||
mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
|
||||
mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
|
||||
// Should we be having a separate translation step apart from the measuring above?
|
||||
// The following only applies to large screen for now, but for future reference
|
||||
// we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
|
||||
// translation directions
|
||||
mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
|
||||
mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
|
||||
mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
|
||||
mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
|
||||
} else {
|
||||
// Currently being split with this taskView, let the non-split selected thumbnail
|
||||
// take up full thumbnail area
|
||||
TaskIdAttributeContainer container =
|
||||
mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
|
||||
container.getThumbnailView().measure(widthMeasureSpec,
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
heightSize -
|
||||
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
|
||||
MeasureSpec.EXACTLY));
|
||||
}
|
||||
updateIconPlacement();
|
||||
}
|
||||
|
||||
@@ -354,9 +391,7 @@ public class GroupedTaskView extends TaskView {
|
||||
// Value set by super call
|
||||
float scale = mIconView.getAlpha();
|
||||
mIconView2.setAlpha(scale);
|
||||
mDigitalWellBeingToast2.updateBannerOffset(1f - scale,
|
||||
mCurrentFullscreenParams.mCurrentDrawnInsets.top
|
||||
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
|
||||
mDigitalWellBeingToast2.updateBannerOffset(1f - scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -379,21 +414,27 @@ public class GroupedTaskView extends TaskView {
|
||||
mSnapshotView2.refreshSplashView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetViewTransforms() {
|
||||
super.resetViewTransforms();
|
||||
mSnapshotView2.resetViewTransforms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility for thumbnails and associated elements (DWB banners).
|
||||
* IconView is unaffected.
|
||||
* Sets visibility for thumbnails and associated elements (DWB banners).
|
||||
* IconView is unaffected.
|
||||
*
|
||||
* When setting INVISIBLE, sets the visibility for the last selected child task.
|
||||
* When setting VISIBLE (as a reset), sets the visibility for both tasks.
|
||||
* When setting INVISIBLE, sets the visibility for the last selected child task.
|
||||
* When setting VISIBLE (as a reset), sets the visibility for both tasks.
|
||||
*/
|
||||
@Override
|
||||
void setThumbnailVisibility(int visibility) {
|
||||
void setThumbnailVisibility(int visibility, int taskId) {
|
||||
if (visibility == VISIBLE) {
|
||||
mSnapshotView.setVisibility(visibility);
|
||||
mDigitalWellBeingToast.setBannerVisibility(visibility);
|
||||
mSnapshotView2.setVisibility(visibility);
|
||||
mDigitalWellBeingToast2.setBannerVisibility(visibility);
|
||||
} else if (getLastSelectedChildTaskIndex() == 0) {
|
||||
} else if (taskId == getTaskIds()[0]) {
|
||||
mSnapshotView.setVisibility(visibility);
|
||||
mDigitalWellBeingToast.setBannerVisibility(visibility);
|
||||
} else {
|
||||
|
||||
@@ -184,6 +184,7 @@ import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
|
||||
import com.android.quickstep.util.SplitAnimationTimings;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.SurfaceTransaction;
|
||||
@@ -192,6 +193,7 @@ import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.TaskVisualsChangeListener;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.VibrationConstants;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
import com.android.systemui.plugins.ResourceProvider;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
@@ -935,7 +937,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
if (mHandleTaskStackChanges) {
|
||||
TaskView taskView = getTaskViewByTaskId(taskId);
|
||||
if (taskView != null) {
|
||||
for (TaskView.TaskIdAttributeContainer container :
|
||||
for (TaskIdAttributeContainer container :
|
||||
taskView.getTaskIdAttributeContainers()) {
|
||||
if (container == null || taskId != container.getTask().key.id) {
|
||||
continue;
|
||||
@@ -3099,29 +3101,26 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
|
||||
RectF startingTaskRect = new RectF();
|
||||
safeRemoveDragLayerView(mFirstFloatingTaskView);
|
||||
SplitAnimInitProps splitAnimInitProps =
|
||||
mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
|
||||
() -> mSplitHiddenTaskView, () -> mSplitSelectSource);
|
||||
if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
|
||||
// Create the split select animation from Overview
|
||||
mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
|
||||
anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
|
||||
mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
|
||||
mSplitSelectStateController.getInitialTaskId());
|
||||
anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
|
||||
timings.getIconFadeStartOffset(),
|
||||
timings.getIconFadeEndOffset()));
|
||||
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
|
||||
mSplitHiddenTaskView.getThumbnail(),
|
||||
mSplitHiddenTaskView.getThumbnail().getThumbnail(),
|
||||
mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
|
||||
mFirstFloatingTaskView.setAlpha(1);
|
||||
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
|
||||
true /* fadeWithThumbnail */, true /* isStagedTask */);
|
||||
} else {
|
||||
// Create the split select animation from Home
|
||||
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
|
||||
mSplitSelectSource.view, null /* thumbnail */,
|
||||
mSplitSelectSource.drawable, startingTaskRect);
|
||||
mFirstFloatingTaskView.setAlpha(1);
|
||||
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
|
||||
false /* fadeWithThumbnail */, true /* isStagedTask */);
|
||||
}
|
||||
|
||||
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
|
||||
splitAnimInitProps.getOriginalView(),
|
||||
splitAnimInitProps.getOriginalBitmap(),
|
||||
splitAnimInitProps.getIconDrawable(), startingTaskRect);
|
||||
mFirstFloatingTaskView.setAlpha(1);
|
||||
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
|
||||
splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
|
||||
|
||||
// Allow user to click staged app to launch into fullscreen
|
||||
if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
|
||||
mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
|
||||
@@ -4450,7 +4449,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
mTaskViewsSecondarySplitTranslation = translation;
|
||||
for (int i = 0; i < getTaskViewCount(); i++) {
|
||||
TaskView taskView = requireTaskViewAt(i);
|
||||
if (taskView == mSplitHiddenTaskView) {
|
||||
if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
|
||||
continue;
|
||||
}
|
||||
taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
|
||||
@@ -4501,6 +4500,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
|
||||
mSplitSelectStateController
|
||||
.setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal);
|
||||
|
||||
// Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
|
||||
mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
|
||||
&& mSplitHiddenTaskView.containsMultipleTasks());
|
||||
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
|
||||
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
|
||||
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
|
||||
@@ -4519,8 +4522,32 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
* Modifies a PendingAnimation with the animations for entering split staging
|
||||
*/
|
||||
public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
|
||||
if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
|
||||
// Splitting from Overview
|
||||
boolean isInitiatingSplitFromTaskView =
|
||||
mSplitSelectStateController.isAnimateCurrentTaskDismissal();
|
||||
boolean isInitiatingTaskViewSplitPair =
|
||||
mSplitSelectStateController.isDismissingFromSplitPair();
|
||||
if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
|
||||
// Splitting from Overview for split pair task
|
||||
createInitialSplitSelectAnimation(builder);
|
||||
|
||||
// Animate pair thumbnail into full thumbnail
|
||||
boolean primaryTaskSelected =
|
||||
mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
|
||||
mSplitSelectStateController.getInitialTaskId();
|
||||
TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
|
||||
.getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
|
||||
TaskThumbnailView thumbnail = taskIdAttributeContainer.getThumbnailView();
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.addInitialSplitFromPair(taskIdAttributeContainer, builder,
|
||||
mActivity.getDeviceProfile(),
|
||||
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
|
||||
primaryTaskSelected);
|
||||
builder.addOnFrameCallback(() ->{
|
||||
thumbnail.refreshSplashView();
|
||||
mSplitHiddenTaskView.updateSnapshotRadius();
|
||||
});
|
||||
} else if (isInitiatingSplitFromTaskView) {
|
||||
// Splitting from Overview for fullscreen task
|
||||
createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
|
||||
true /* dismissingForSplitSelection*/);
|
||||
} else {
|
||||
@@ -4608,7 +4635,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
|
||||
mSecondSplitHiddenView = containerTaskView;
|
||||
if (mSecondSplitHiddenView != null) {
|
||||
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
|
||||
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
|
||||
mSplitSelectStateController.getSecondTaskId());
|
||||
}
|
||||
|
||||
InteractionJankMonitorWrapper.begin(this,
|
||||
@@ -4634,7 +4662,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
}
|
||||
|
||||
if (mSecondSplitHiddenView != null) {
|
||||
mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
|
||||
mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
|
||||
mSecondSplitHiddenView = null;
|
||||
}
|
||||
|
||||
@@ -4660,7 +4688,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
resetTaskVisuals();
|
||||
mSplitHiddenTaskViewIndex = -1;
|
||||
if (mSplitHiddenTaskView != null) {
|
||||
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
|
||||
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
|
||||
mSplitHiddenTaskView = null;
|
||||
}
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
@@ -5534,7 +5562,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
}
|
||||
|
||||
taskView.setShowScreenshot(true);
|
||||
for (TaskView.TaskIdAttributeContainer container :
|
||||
for (TaskIdAttributeContainer container :
|
||||
taskView.getTaskIdAttributeContainers()) {
|
||||
if (container == null) {
|
||||
continue;
|
||||
|
||||
@@ -81,6 +81,47 @@ public class TaskThumbnailView extends View {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Property<TaskThumbnailView, Float> SPLASH_ALPHA =
|
||||
new FloatProperty<TaskThumbnailView>("splashAlpha") {
|
||||
@Override
|
||||
public void setValue(TaskThumbnailView thumbnail, float splashAlpha) {
|
||||
thumbnail.setSplashAlpha(splashAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(TaskThumbnailView thumbnailView) {
|
||||
return thumbnailView.mSplashAlpha / 255f;
|
||||
}
|
||||
};
|
||||
|
||||
/** Use to animate thumbnail translationX while first app in split selection is initiated */
|
||||
public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_X =
|
||||
new FloatProperty<TaskThumbnailView>("splitSelectTranslateX") {
|
||||
@Override
|
||||
public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) {
|
||||
thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(TaskThumbnailView thumbnailView) {
|
||||
return thumbnailView.mSplitSelectTranslateX;
|
||||
}
|
||||
};
|
||||
|
||||
/** Use to animate thumbnail translationY while first app in split selection is initiated */
|
||||
public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_Y =
|
||||
new FloatProperty<TaskThumbnailView>("splitSelectTranslateY") {
|
||||
@Override
|
||||
public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) {
|
||||
thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(TaskThumbnailView thumbnailView) {
|
||||
return thumbnailView.mSplitSelectTranslateY;
|
||||
}
|
||||
};
|
||||
|
||||
private final BaseActivity mActivity;
|
||||
@Nullable
|
||||
private TaskOverlay mOverlay;
|
||||
@@ -111,6 +152,10 @@ public class TaskThumbnailView extends View {
|
||||
private int mSplashAlpha = 0;
|
||||
|
||||
private boolean mOverlayEnabled;
|
||||
/** Used as a placeholder when the original thumbnail animates out to. */
|
||||
private boolean mShowSplashForSplitSelection;
|
||||
private float mSplitSelectTranslateX;
|
||||
private float mSplitSelectTranslateY;
|
||||
|
||||
public TaskThumbnailView(Context context) {
|
||||
this(context, null);
|
||||
@@ -293,16 +338,9 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
|
||||
canvas.save();
|
||||
canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
|
||||
canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
|
||||
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
|
||||
drawOnCanvas(canvas,
|
||||
-currentDrawnInsets.left,
|
||||
-currentDrawnInsets.top,
|
||||
getMeasuredWidth() + currentDrawnInsets.right,
|
||||
getMeasuredHeight() + currentDrawnInsets.bottom,
|
||||
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
|
||||
mFullscreenParams.mCurrentDrawnCornerRadius);
|
||||
canvas.restore();
|
||||
}
|
||||
@@ -342,10 +380,17 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
// Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
|
||||
if (shouldShowSplashView()) {
|
||||
float cornerRadiusX = cornerRadius;
|
||||
float cornerRadiusY = cornerRadius;
|
||||
if (mShowSplashForSplitSelection) {
|
||||
cornerRadiusX = cornerRadius / getScaleX();
|
||||
cornerRadiusY = cornerRadius / getScaleY();
|
||||
}
|
||||
|
||||
// Always draw background for hiding inconsistencies, even if splash view is not yet
|
||||
// loaded (which can happen as task icons are loaded asynchronously in the background)
|
||||
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
|
||||
cornerRadius, mSplashBackgroundPaint);
|
||||
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX,
|
||||
cornerRadiusY, mSplashBackgroundPaint);
|
||||
if (mSplashView != null) {
|
||||
mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
|
||||
mSplashView.draw(canvas);
|
||||
@@ -353,6 +398,31 @@ public class TaskThumbnailView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/** See {@link #SPLIT_SELECT_TRANSLATE_X} */
|
||||
protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
|
||||
mSplitSelectTranslateX = splitSelectTranslateX;
|
||||
applyTranslateX();
|
||||
}
|
||||
|
||||
/** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
|
||||
protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
|
||||
mSplitSelectTranslateY = splitSelectTranslateY;
|
||||
applyTranslateY();
|
||||
}
|
||||
|
||||
private void applyTranslateX() {
|
||||
setTranslationX(mSplitSelectTranslateX);
|
||||
}
|
||||
|
||||
private void applyTranslateY() {
|
||||
setTranslationY(mSplitSelectTranslateY);
|
||||
}
|
||||
|
||||
protected void resetViewTransforms() {
|
||||
mSplitSelectTranslateX = 0;
|
||||
mSplitSelectTranslateY = 0;
|
||||
}
|
||||
|
||||
public TaskView getTaskView() {
|
||||
return (TaskView) getParent();
|
||||
}
|
||||
@@ -373,7 +443,12 @@ public class TaskThumbnailView extends View {
|
||||
*/
|
||||
public boolean shouldShowSplashView() {
|
||||
return isThumbnailAspectRatioDifferentFromThumbnailData()
|
||||
|| isThumbnailRotationDifferentFromTask();
|
||||
|| isThumbnailRotationDifferentFromTask()
|
||||
|| mShowSplashForSplitSelection;
|
||||
}
|
||||
|
||||
public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
|
||||
mShowSplashForSplitSelection = showSplashForSplitSelection;
|
||||
}
|
||||
|
||||
protected void refreshSplashView() {
|
||||
@@ -396,7 +471,6 @@ public class TaskThumbnailView extends View {
|
||||
|
||||
imageView.setScaleType(ImageView.ScaleType.MATRIX);
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
|
||||
float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
|
||||
float viewWidth = getMeasuredWidth();
|
||||
@@ -408,12 +482,13 @@ public class TaskThumbnailView extends View {
|
||||
float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
|
||||
float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
|
||||
? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
|
||||
float scale = nonGridScale * recentsMaxScale;
|
||||
float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
|
||||
float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
|
||||
|
||||
// Center the image in the view.
|
||||
matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
|
||||
// Apply scale transformation after translation, pivoting around center of view.
|
||||
matrix.postScale(scale, scale, viewCenterX, viewCenterY);
|
||||
matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY);
|
||||
|
||||
imageView.setImageMatrix(matrix);
|
||||
mSplashView = imageView;
|
||||
@@ -424,9 +499,7 @@ public class TaskThumbnailView extends View {
|
||||
return false;
|
||||
}
|
||||
|
||||
RectF insets = mPreviewPositionHelper.getClippedInsets();
|
||||
float thumbnailViewAspect = (getWidth() + insets.left + insets.right)
|
||||
/ (getHeight() + insets.top + insets.bottom);
|
||||
float thumbnailViewAspect = getWidth() / (float) getHeight();
|
||||
float thumbnailDataAspect =
|
||||
mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.quickstep.views;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
@@ -41,6 +42,7 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.IdRes;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
@@ -82,7 +84,6 @@ import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
@@ -559,9 +560,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
}
|
||||
mModalness = modalness;
|
||||
mIconView.setAlpha(1 - modalness);
|
||||
mDigitalWellBeingToast.updateBannerOffset(modalness,
|
||||
mCurrentFullscreenParams.mCurrentDrawnInsets.top
|
||||
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
|
||||
mDigitalWellBeingToast.updateBannerOffset(modalness);
|
||||
}
|
||||
|
||||
public DigitalWellBeingToast getDigitalWellBeingToast() {
|
||||
@@ -706,9 +705,12 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
}
|
||||
SplitSelectStateController splitSelectStateController =
|
||||
recentsView.getSplitSelectController();
|
||||
if (splitSelectStateController.isSplitSelectActive() &&
|
||||
splitSelectStateController.getInitialTaskId() == getTask().key.id) {
|
||||
// Prevent taps on the this taskview if it's being animated into split select state
|
||||
// Disable taps for split selection animation unless we have multiple tasks
|
||||
boolean disableTapsForSplitSelect =
|
||||
splitSelectStateController.isSplitSelectActive()
|
||||
&& splitSelectStateController.getInitialTaskId() == getTask().key.id
|
||||
&& !containsMultipleTasks();
|
||||
if (disableTapsForSplitSelect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -718,6 +720,25 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return taskId that split selection was initiated with,
|
||||
* {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
|
||||
* split selection
|
||||
*/
|
||||
protected int getThisTaskCurrentlyInSplitSelection() {
|
||||
SplitSelectStateController splitSelectController =
|
||||
getRecentsView().getSplitSelectController();
|
||||
int initSplitTaskId = INVALID_TASK_ID;
|
||||
for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
|
||||
int taskId = container.getTask().key.id;
|
||||
if (taskId == splitSelectController.getInitialTaskId()) {
|
||||
initSplitTaskId = taskId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return initSplitTaskId;
|
||||
}
|
||||
|
||||
private void onClick(View view) {
|
||||
if (getTask() == null) {
|
||||
return;
|
||||
@@ -747,6 +768,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
|
||||
/**
|
||||
* Returns the task index of the last selected child task (0 or 1).
|
||||
* If we contain multiple tasks and this TaskView is used as part of split selection, the
|
||||
* selected child task index will be that of the remaining task.
|
||||
*/
|
||||
protected int getLastSelectedChildTaskIndex() {
|
||||
return 0;
|
||||
@@ -1084,6 +1107,8 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
|
||||
int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
|
||||
|
||||
// TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
|
||||
// of a hybrid of both margins and translations
|
||||
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
|
||||
snapshotParams.topMargin = thumbnailTopMargin;
|
||||
mSnapshotView.setLayoutParams(snapshotParams);
|
||||
@@ -1120,9 +1145,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
|
||||
.getInterpolation(progress);
|
||||
mIconView.setAlpha(scale);
|
||||
mDigitalWellBeingToast.updateBannerOffset(1f - scale,
|
||||
mCurrentFullscreenParams.mCurrentDrawnInsets.top
|
||||
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
|
||||
mDigitalWellBeingToast.updateBannerOffset(1f - scale);
|
||||
}
|
||||
|
||||
public void setIconScaleAnimStartProgress(float startProgress) {
|
||||
@@ -1179,6 +1202,7 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
setAlpha(mStableAlpha);
|
||||
setIconScaleAndDim(1);
|
||||
setColorTint(0, 0);
|
||||
mSnapshotView.resetViewTransforms();
|
||||
}
|
||||
|
||||
public void setStableAlpha(float parentAlpha) {
|
||||
@@ -1720,10 +1744,12 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
|
||||
* IconView is unaffected.
|
||||
* Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
|
||||
* IconView is unaffected.
|
||||
*
|
||||
* @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
|
||||
*/
|
||||
void setThumbnailVisibility(int visibility) {
|
||||
void setThumbnailVisibility(int visibility, int taskId) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != mIconView) {
|
||||
@@ -1740,17 +1766,11 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
private final float mCornerRadius;
|
||||
private final float mWindowCornerRadius;
|
||||
|
||||
public RectF mCurrentDrawnInsets = new RectF();
|
||||
public float mCurrentDrawnCornerRadius;
|
||||
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
|
||||
public float mScale = 1;
|
||||
|
||||
private boolean mIsTaskbarTransient;
|
||||
|
||||
public FullscreenDrawParams(Context context) {
|
||||
mCornerRadius = TaskCornerRadius.get(context);
|
||||
mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
|
||||
mIsTaskbarTransient = DisplayController.isTransientTaskbar(context);
|
||||
|
||||
mCurrentDrawnCornerRadius = mCornerRadius;
|
||||
}
|
||||
@@ -1760,36 +1780,9 @@ public class TaskView extends FrameLayout implements Reusable {
|
||||
*/
|
||||
public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
|
||||
int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
|
||||
RectF insets = getInsetsToDrawInFullscreen(pph, dp, mIsTaskbarTransient);
|
||||
|
||||
float currentInsetsLeft = insets.left * fullscreenProgress;
|
||||
float currentInsetsTop = insets.top * fullscreenProgress;
|
||||
float currentInsetsRight = insets.right * fullscreenProgress;
|
||||
float currentInsetsBottom = insets.bottom * fullscreenProgress;
|
||||
mCurrentDrawnInsets.set(
|
||||
currentInsetsLeft, currentInsetsTop, currentInsetsRight, currentInsetsBottom);
|
||||
|
||||
mCurrentDrawnCornerRadius =
|
||||
Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
|
||||
/ parentScale / taskViewScale;
|
||||
|
||||
// We scaled the thumbnail to fit the content (excluding insets) within task view width.
|
||||
// Now that we are drawing left/right insets again, we need to scale down to fit them.
|
||||
if (previewWidth > 0) {
|
||||
mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
|
||||
*/
|
||||
private static RectF getInsetsToDrawInFullscreen(PreviewPositionHelper pph,
|
||||
DeviceProfile dp, boolean isTaskbarTransient) {
|
||||
if (dp.isTaskbarPresent && isTaskbarTransient) {
|
||||
return pph.getClippedInsets();
|
||||
}
|
||||
return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
|
||||
? pph.getClippedInsets() : EMPTY_RECT_F;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
package com.android.quickstep
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.FakeInvariantDeviceProfileTest
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
|
||||
import com.android.quickstep.util.TaskCornerRadius
|
||||
import com.android.quickstep.views.TaskView.FullscreenDrawParams
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData
|
||||
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
|
||||
import com.android.wm.shell.util.SplitBounds
|
||||
import com.android.systemui.shared.system.QuickStepContract
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.math.roundToInt
|
||||
import org.junit.Before
|
||||
@@ -50,7 +48,42 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets() {
|
||||
fun setStartProgress_correctCornerRadiusForTablet() {
|
||||
initializeVarsForTablet()
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
|
||||
val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
|
||||
val currentRotation = 0
|
||||
val isRtl = false
|
||||
|
||||
mPreviewPositionHelper.updateThumbnailMatrix(
|
||||
previewRect,
|
||||
mThumbnailData,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
dp.widthPx,
|
||||
dp.heightPx,
|
||||
dp.taskbarSize,
|
||||
dp.isTablet,
|
||||
currentRotation,
|
||||
isRtl
|
||||
)
|
||||
params.setProgress(
|
||||
/* fullscreenProgress= */ 0f,
|
||||
/* parentScale= */ 1.0f,
|
||||
/* taskViewScale= */ 1.0f,
|
||||
/* previewWidth= */ 0,
|
||||
dp,
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
val expectedRadius = TaskCornerRadius.get(context)
|
||||
assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_correctCornerRadiusForTablet() {
|
||||
initializeVarsForTablet()
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
@@ -80,122 +113,19 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize * TASK_SCALE)
|
||||
assertThat(params.mCurrentDrawnInsets).isEqualTo(expectedClippedInsets)
|
||||
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
|
||||
assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets_splitPortrait() {
|
||||
initializeVarsForTablet()
|
||||
fun setStartProgress_correctCornerRadiusForPhone() {
|
||||
initializeVarsForPhone()
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
|
||||
val canvasHeight = (dp.heightPx * TASK_SCALE / 2).roundToInt()
|
||||
val currentRotation = 0
|
||||
val isRtl = false
|
||||
// portrait/vertical split apps
|
||||
val dividerSize = 10
|
||||
val splitBounds =
|
||||
SplitBounds(
|
||||
Rect(0, 0, dp.widthPx, (dp.heightPx - dividerSize) / 2),
|
||||
Rect(0, (dp.heightPx + dividerSize) / 2, dp.widthPx, dp.heightPx),
|
||||
0 /*lefTopTaskId*/,
|
||||
0 /*rightBottomTaskId*/
|
||||
)
|
||||
mPreviewPositionHelper.setSplitBounds(splitBounds, STAGE_POSITION_BOTTOM_OR_RIGHT)
|
||||
|
||||
mPreviewPositionHelper.updateThumbnailMatrix(
|
||||
previewRect,
|
||||
mThumbnailData,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
dp.widthPx,
|
||||
dp.heightPx,
|
||||
dp.taskbarSize,
|
||||
dp.isTablet,
|
||||
currentRotation,
|
||||
isRtl
|
||||
)
|
||||
params.setProgress(
|
||||
/* fullscreenProgress= */ 1.0f,
|
||||
/* parentScale= */ 1.0f,
|
||||
/* taskViewScale= */ 1.0f,
|
||||
/* previewWidth= */ 0,
|
||||
dp,
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
// Probably unhelpful, but also unclear how to test otherwise ¯\_(ツ)_/¯
|
||||
val fullscreenTaskHeight =
|
||||
dp.heightPx * (1 - (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent))
|
||||
val canvasScreenRatio = canvasHeight / fullscreenTaskHeight
|
||||
val expectedBottomHint = dp.taskbarSize * canvasScreenRatio
|
||||
assertThat(params.mCurrentDrawnInsets.bottom).isWithin(1f).of(expectedBottomHint)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromTopForTablets_splitPortrait() {
|
||||
initializeVarsForTablet()
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
|
||||
val canvasHeight = (dp.heightPx * TASK_SCALE / 2).roundToInt()
|
||||
val currentRotation = 0
|
||||
val isRtl = false
|
||||
// portrait/vertical split apps
|
||||
val dividerSize = 10
|
||||
val splitBounds =
|
||||
SplitBounds(
|
||||
Rect(0, 0, dp.widthPx, (dp.heightPx - dividerSize) / 2),
|
||||
Rect(0, (dp.heightPx + dividerSize) / 2, dp.widthPx, dp.heightPx),
|
||||
0 /*lefTopTaskId*/,
|
||||
0 /*rightBottomTaskId*/
|
||||
)
|
||||
mPreviewPositionHelper.setSplitBounds(splitBounds, STAGE_POSITION_TOP_OR_LEFT)
|
||||
|
||||
mPreviewPositionHelper.updateThumbnailMatrix(
|
||||
previewRect,
|
||||
mThumbnailData,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
dp.widthPx,
|
||||
dp.heightPx,
|
||||
dp.taskbarSize,
|
||||
dp.isTablet,
|
||||
currentRotation,
|
||||
isRtl
|
||||
)
|
||||
params.setProgress(
|
||||
/* fullscreenProgress= */ 1.0f,
|
||||
/* parentScale= */ 1.0f,
|
||||
/* taskViewScale= */ 1.0f,
|
||||
/* previewWidth= */ 0,
|
||||
dp,
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
assertThat(params.mCurrentDrawnInsets.bottom).isWithin(1f).of((0f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets_splitLandscape() {
|
||||
initializeVarsForTablet(isLandscape = true)
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
val canvasWidth = (dp.widthPx * TASK_SCALE / 2).roundToInt()
|
||||
val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
|
||||
val currentRotation = 0
|
||||
val isRtl = false
|
||||
// portrait/vertical split apps
|
||||
val dividerSize = 10
|
||||
val splitBounds =
|
||||
SplitBounds(
|
||||
Rect(0, 0, (dp.widthPx - dividerSize) / 2, dp.heightPx),
|
||||
Rect((dp.widthPx + dividerSize) / 2, 0, dp.widthPx, dp.heightPx),
|
||||
0 /*lefTopTaskId*/,
|
||||
0 /*rightBottomTaskId*/
|
||||
)
|
||||
mPreviewPositionHelper.setSplitBounds(splitBounds, STAGE_POSITION_BOTTOM_OR_RIGHT)
|
||||
|
||||
mPreviewPositionHelper.updateThumbnailMatrix(
|
||||
previewRect,
|
||||
@@ -210,7 +140,7 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
isRtl
|
||||
)
|
||||
params.setProgress(
|
||||
/* fullscreenProgress= */ 1.0f,
|
||||
/* fullscreenProgress= */ 0f,
|
||||
/* parentScale= */ 1.0f,
|
||||
/* taskViewScale= */ 1.0f,
|
||||
/* previewWidth= */ 0,
|
||||
@@ -218,11 +148,12 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
assertThat(params.mCurrentDrawnInsets.bottom).isWithin(1f).of((dp.taskbarSize * TASK_SCALE))
|
||||
val expectedRadius = TaskCornerRadius.get(context)
|
||||
assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setFullProgress_currentDrawnInsets_doNotClipTaskbarSizeFromBottomForPhones() {
|
||||
fun setFullProgress_correctCornerRadiusForPhone() {
|
||||
initializeVarsForPhone()
|
||||
val dp = newDP()
|
||||
val previewRect = Rect(0, 0, 100, 100)
|
||||
@@ -252,7 +183,7 @@ class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
|
||||
mPreviewPositionHelper
|
||||
)
|
||||
|
||||
val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
|
||||
assertThat(params.mCurrentDrawnInsets).isEqualTo(expectedClippedInsets)
|
||||
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
|
||||
assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
android:id="@+id/icon"
|
||||
android:singleLine="false"
|
||||
android:lines="2"
|
||||
android:inputType="textMultiLine"
|
||||
launcher:iconDisplay="all_apps"
|
||||
launcher:centerVertically="true" />
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<!-- Split display specific version of Launcher3/res/xml/default_workspace_4x4.xml -->
|
||||
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
|
||||
|
||||
<!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
|
||||
<favorite
|
||||
launcher:container="-101"
|
||||
launcher:screen="0"
|
||||
launcher:x="0"
|
||||
launcher:y="0"
|
||||
launcher:className="com.google.android.apps.chrome.Main"
|
||||
launcher:packageName="com.android.chrome" />
|
||||
|
||||
</favorites>
|
||||
@@ -2,6 +2,7 @@ package com.android.launcher3;
|
||||
|
||||
import static android.os.Process.myUserHandle;
|
||||
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -51,10 +52,10 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
*/
|
||||
@WorkerThread
|
||||
public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds,
|
||||
@NonNull LauncherWidgetHolder holder) {
|
||||
@NonNull AppWidgetHost host) {
|
||||
if (WidgetsModel.GO_DISABLE_WIDGETS) {
|
||||
Log.e(TAG, "Skipping widget ID remap as widgets not supported");
|
||||
holder.deleteHost();
|
||||
host.deleteHost();
|
||||
return;
|
||||
}
|
||||
if (!RestoreDbTask.isPending(context)) {
|
||||
@@ -63,7 +64,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
Log.e(TAG, "Skipping widget ID remap as DB already in use");
|
||||
for (int widgetId : newWidgetIds) {
|
||||
Log.d(TAG, "Deleting widgetId: " + widgetId);
|
||||
holder.deleteAppWidgetId(widgetId);
|
||||
host.deleteAppWidgetId(widgetId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
// The widget no long exists.
|
||||
holder.deleteAppWidgetId(newWidgetIds[i]);
|
||||
host.deleteAppWidgetId(newWidgetIds[i]);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
|
||||
@@ -52,8 +52,10 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dot.DotInfo;
|
||||
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
@@ -71,6 +73,8 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||
import com.android.launcher3.util.MultiTranslateDelegate;
|
||||
import com.android.launcher3.search.StringMatcherUtility;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.ShortcutUtil;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
@@ -97,11 +101,19 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
|
||||
private static final float MIN_LETTER_SPACING = -0.05f;
|
||||
private static final int MAX_SEARCH_LOOP_COUNT = 20;
|
||||
private static final Character NEW_LINE = '\n';
|
||||
private static final String EMPTY = "";
|
||||
private static final StringMatcherUtility.StringMatcher MATCHER =
|
||||
StringMatcherUtility.StringMatcher.getInstance();
|
||||
|
||||
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
|
||||
|
||||
private float mScaleForReorderBounce = 1f;
|
||||
|
||||
private IntArray mBreakPointsIntArray;
|
||||
private CharSequence mLastOriginalText;
|
||||
private CharSequence mLastModifiedText;
|
||||
|
||||
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
|
||||
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
|
||||
@Override
|
||||
@@ -134,7 +146,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
private FastBitmapDrawable mIcon;
|
||||
private boolean mCenterVertically;
|
||||
|
||||
protected final int mDisplay;
|
||||
protected int mDisplay;
|
||||
|
||||
private final CheckLongPressHelper mLongPressHelper;
|
||||
|
||||
@@ -255,6 +267,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
mDotParams.scale = 0f;
|
||||
mForceHideDot = false;
|
||||
setBackground(null);
|
||||
setSingleLine(true);
|
||||
setMaxLines(1);
|
||||
|
||||
setTag(null);
|
||||
if (mIconLoadRequest != null) {
|
||||
@@ -382,8 +396,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void applyLabel(ItemInfoWithIcon info) {
|
||||
setText(info.title);
|
||||
@VisibleForTesting
|
||||
public void applyLabel(ItemInfoWithIcon info) {
|
||||
CharSequence label = info.title;
|
||||
if (label != null) {
|
||||
mLastOriginalText = label;
|
||||
mLastModifiedText = mLastOriginalText;
|
||||
mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
|
||||
setText(label);
|
||||
}
|
||||
if (info.contentDescription != null) {
|
||||
setContentDescription(info.isDisabled()
|
||||
? getContext().getString(R.string.disabled_app_label, info.contentDescription)
|
||||
@@ -391,6 +412,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
}
|
||||
}
|
||||
|
||||
/** This is used for testing to forcefully set the display to ALL_APPS */
|
||||
@VisibleForTesting
|
||||
public void setDisplayAllApps() {
|
||||
mDisplay = DISPLAY_ALL_APPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default long press timeout.
|
||||
*/
|
||||
@@ -637,6 +664,27 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
|
||||
getPaddingBottom());
|
||||
}
|
||||
// only apply two line for all_apps
|
||||
if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && (mLastOriginalText != null)
|
||||
&& (mDisplay == DISPLAY_ALL_APPS)) {
|
||||
CharSequence modifiedString = modifyTitleToSupportMultiLine(
|
||||
MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
|
||||
- getCompoundPaddingRight(),
|
||||
mLastOriginalText,
|
||||
getPaint(), mBreakPointsIntArray);
|
||||
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
|
||||
mLastModifiedText = modifiedString;
|
||||
setText(modifiedString);
|
||||
// if text contains NEW_LINE, set max lines to 2
|
||||
if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) {
|
||||
setSingleLine(false);
|
||||
setMaxLines(2);
|
||||
} else {
|
||||
setSingleLine(true);
|
||||
setMaxLines(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@@ -697,6 +745,73 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
return ObjectAnimator.ofFloat(this, TEXT_ALPHA_PROPERTY, toAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new string that will support two line text depending on the current string.
|
||||
* This method calculates the limited width of a text view and creates a string to fit as
|
||||
* many words as it can until the limit is reached. Once the limit is reached, we decide to
|
||||
* either return the original title or continue on a new line. How to get the new string is by
|
||||
* iterating through the list of break points and determining if the strings between the break
|
||||
* points can fit within the line it is in.
|
||||
* Example assuming each character takes up one spot:
|
||||
* title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
|
||||
* We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
|
||||
* now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth
|
||||
* at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking
|
||||
* if the first char is a SPACE, we trim to append "Stats". So resulting string would be
|
||||
* "Battery\nStats"
|
||||
*/
|
||||
public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, CharSequence title,
|
||||
TextPaint paint, IntArray breakPoints) {
|
||||
// current title is less than the width allowed so we can just skip
|
||||
if (title == null || paint.measureText(title, 0, title.length()) <= limitedWidth) {
|
||||
return title;
|
||||
}
|
||||
float currentWordWidth, runningWidth = 0;
|
||||
CharSequence currentWord;
|
||||
StringBuilder newString = new StringBuilder();
|
||||
int stringPtr = 0;
|
||||
for (int i = 0; i < breakPoints.size()+1; i++) {
|
||||
if (i < breakPoints.size()) {
|
||||
currentWord = title.subSequence(stringPtr, breakPoints.get(i)+1);
|
||||
} else {
|
||||
// last word from recent breakpoint until the end of the string
|
||||
currentWord = title.subSequence(stringPtr, title.length());
|
||||
}
|
||||
currentWordWidth = paint.measureText(currentWord,0, currentWord.length());
|
||||
runningWidth += currentWordWidth;
|
||||
if (runningWidth <= limitedWidth) {
|
||||
newString.append(currentWord);
|
||||
} else {
|
||||
// there is no more space
|
||||
if (i == 0) {
|
||||
// if the first words exceeds width, just return as the first line will ellipse
|
||||
return title;
|
||||
} else {
|
||||
// If putting word onto a new line, make sure there is no space or new line
|
||||
// character in the beginning of the current word and just put in the rest of
|
||||
// the characters.
|
||||
CharSequence lastCharacters = title.subSequence(stringPtr, title.length());
|
||||
int beginningLetterType =
|
||||
Character.getType(Character.codePointAt(lastCharacters,0));
|
||||
if (beginningLetterType == Character.SPACE_SEPARATOR
|
||||
|| beginningLetterType == Character.LINE_SEPARATOR) {
|
||||
lastCharacters = lastCharacters.length() > 1
|
||||
? lastCharacters.subSequence(1, lastCharacters.length())
|
||||
: EMPTY;
|
||||
}
|
||||
newString.append(NEW_LINE).append(lastCharacters);
|
||||
return newString.toString();
|
||||
}
|
||||
}
|
||||
if (i >= breakPoints.size()) {
|
||||
// no need to look forward into the string if we've already finished processing
|
||||
break;
|
||||
}
|
||||
stringPtr = breakPoints.get(i)+1;
|
||||
}
|
||||
return newString.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelLongPress() {
|
||||
super.cancelLongPress();
|
||||
@@ -717,7 +832,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
|| info.hasPromiseIconUi()
|
||||
|| (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
|
||||
|| (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
|
||||
updateProgressBarUi(icon);
|
||||
updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public class DeviceProfile {
|
||||
private final int mMinHotseatIconSpacePx;
|
||||
private final int mMinHotseatQsbWidthPx;
|
||||
private final int mMaxHotseatIconSpacePx;
|
||||
private final int mInlineNavButtonsEndSpacingPx;
|
||||
public final int inlineNavButtonsEndSpacingPx;
|
||||
|
||||
// Bottom sheets
|
||||
public int bottomSheetTopPadding;
|
||||
@@ -490,7 +490,7 @@ public class DeviceProfile {
|
||||
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
|
||||
updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
|
||||
if (areNavButtonsInline && !isPhone) {
|
||||
mInlineNavButtonsEndSpacingPx =
|
||||
inlineNavButtonsEndSpacingPx =
|
||||
res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
|
||||
/*
|
||||
* 3 nav buttons +
|
||||
@@ -499,9 +499,9 @@ public class DeviceProfile {
|
||||
*/
|
||||
hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
|
||||
+ 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
|
||||
+ mInlineNavButtonsEndSpacingPx;
|
||||
+ inlineNavButtonsEndSpacingPx;
|
||||
} else {
|
||||
mInlineNavButtonsEndSpacingPx = 0;
|
||||
inlineNavButtonsEndSpacingPx = 0;
|
||||
hotseatBarEndOffset = 0;
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ public class DeviceProfile {
|
||||
}
|
||||
|
||||
// The side space with inline buttons should be what is defined in InvariantDeviceProfile
|
||||
int sideSpacePx = mInlineNavButtonsEndSpacingPx;
|
||||
int sideSpacePx = inlineNavButtonsEndSpacingPx;
|
||||
int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
|
||||
int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
|
||||
hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
|
||||
@@ -1320,7 +1320,7 @@ public class DeviceProfile {
|
||||
int endSpacing;
|
||||
// Hotseat aligns to the left with nav buttons
|
||||
if (hotseatBarEndOffset > 0) {
|
||||
startSpacing = mInlineNavButtonsEndSpacingPx;
|
||||
startSpacing = inlineNavButtonsEndSpacingPx;
|
||||
endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
|
||||
} else {
|
||||
startSpacing = (availableWidthPx - hotseatWidth) / 2;
|
||||
|
||||
@@ -107,6 +107,7 @@ public class LauncherProvider extends ContentProvider {
|
||||
|
||||
private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
|
||||
private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
|
||||
private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
|
||||
|
||||
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
|
||||
|
||||
@@ -410,6 +411,9 @@ public class LauncherProvider extends ContentProvider {
|
||||
case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2:
|
||||
mDefaultWorkspaceLayoutOverride = TEST2_WORKSPACE_LAYOUT_RES_XML;
|
||||
break;
|
||||
case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL:
|
||||
mDefaultWorkspaceLayoutOverride = TAPL_WORKSPACE_LAYOUT_RES_XML;
|
||||
break;
|
||||
default:
|
||||
mDefaultWorkspaceLayoutOverride = 0;
|
||||
break;
|
||||
@@ -550,39 +554,42 @@ public class LauncherProvider extends ContentProvider {
|
||||
Log.d(TAG, "loading default workspace");
|
||||
|
||||
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
|
||||
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
|
||||
if (loader == null) {
|
||||
loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
|
||||
}
|
||||
if (loader == null) {
|
||||
final Partner partner = Partner.get(getContext().getPackageManager());
|
||||
if (partner != null) {
|
||||
int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
|
||||
if (workspaceResId != 0) {
|
||||
loader = new DefaultLayoutParser(getContext(), widgetHolder,
|
||||
mOpenHelper, partner.getResources(), workspaceResId);
|
||||
try {
|
||||
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
|
||||
if (loader == null) {
|
||||
loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
|
||||
}
|
||||
if (loader == null) {
|
||||
final Partner partner = Partner.get(getContext().getPackageManager());
|
||||
if (partner != null) {
|
||||
int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
|
||||
if (workspaceResId != 0) {
|
||||
loader = new DefaultLayoutParser(getContext(), widgetHolder,
|
||||
mOpenHelper, partner.getResources(), workspaceResId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean usingExternallyProvidedLayout = loader != null;
|
||||
if (loader == null) {
|
||||
loader = getDefaultLayoutParser(widgetHolder);
|
||||
}
|
||||
final boolean usingExternallyProvidedLayout = loader != null;
|
||||
if (loader == null) {
|
||||
loader = getDefaultLayoutParser(widgetHolder);
|
||||
}
|
||||
|
||||
// There might be some partially restored DB items, due to buggy restore logic in
|
||||
// previous versions of launcher.
|
||||
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
|
||||
// Populate favorites table with initial favorites
|
||||
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
|
||||
&& usingExternallyProvidedLayout) {
|
||||
// Unable to load external layout. Cleanup and load the internal layout.
|
||||
// There might be some partially restored DB items, due to buggy restore logic in
|
||||
// previous versions of launcher.
|
||||
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
|
||||
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
|
||||
getDefaultLayoutParser(widgetHolder));
|
||||
// Populate favorites table with initial favorites
|
||||
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
|
||||
&& usingExternallyProvidedLayout) {
|
||||
// Unable to load external layout. Cleanup and load the internal layout.
|
||||
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
|
||||
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
|
||||
getDefaultLayoutParser(widgetHolder));
|
||||
}
|
||||
clearFlagEmptyDbCreated();
|
||||
} finally {
|
||||
widgetHolder.destroy();
|
||||
}
|
||||
clearFlagEmptyDbCreated();
|
||||
widgetHolder.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,8 +964,6 @@ public class LauncherProvider extends ContentProvider {
|
||||
allWidgets = holder.getAppWidgetIds();
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
Log.e(TAG, "getAppWidgetIds not supported", e);
|
||||
// Necessary to destroy the holder to free up possible activity context
|
||||
holder.destroy();
|
||||
return;
|
||||
}
|
||||
final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
|
||||
|
||||
@@ -389,6 +389,7 @@ public class LauncherSettings {
|
||||
"set_use_test_workspace_layout_flag";
|
||||
public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST = "default_test_workspace";
|
||||
public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2 = "default_test2_workspace";
|
||||
public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL = "default_tapl_workspace";
|
||||
|
||||
public static final String METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG =
|
||||
"clear_use_test_workspace_layout_flag";
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
@@ -140,7 +141,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
protected final OnClickListener mOnIconClickListener;
|
||||
protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
|
||||
protected OnFocusChangeListener mIconFocusListener;
|
||||
private final int mExtraHeight;
|
||||
private final int mExtraTextHeight;
|
||||
|
||||
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
|
||||
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
|
||||
@@ -152,7 +153,8 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
mOnIconClickListener = mActivityContext.getItemOnClickListener();
|
||||
|
||||
mAdapterProvider = adapterProvider;
|
||||
mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
|
||||
mExtraTextHeight = Utilities.calculateTextHeight(
|
||||
mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +199,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
icon.getLayoutParams().height =
|
||||
mActivityContext.getDeviceProfile().allAppsCellHeightPx;
|
||||
if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
|
||||
icon.getLayoutParams().height += mExtraHeight;
|
||||
icon.getLayoutParams().height += mExtraTextHeight;
|
||||
}
|
||||
return new ViewHolder(icon);
|
||||
case VIEW_TYPE_EMPTY_SEARCH:
|
||||
|
||||
@@ -113,6 +113,10 @@ public final class FeatureFlags {
|
||||
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
|
||||
"ENABLE_TWOLINE_ALLAPPS", false, "Enables two line label inside all apps.");
|
||||
|
||||
public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
|
||||
"ENABLE_TWOLINE_DEVICESEARCH", false,
|
||||
"Enable two line label for icons with labels on device search.");
|
||||
|
||||
public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = getReleaseFlag(
|
||||
270391397, "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", false,
|
||||
"Allows on device search in all apps logging");
|
||||
@@ -302,7 +306,7 @@ public final class FeatureFlags {
|
||||
+ " is enabled or in prefix state");
|
||||
|
||||
public static final BooleanFlag ENABLE_MATERIAL_U_POPUP = getDebugFlag(270395516,
|
||||
"ENABLE_MATERIAL_U_POPUP", false, "Switch popup UX to use material U");
|
||||
"ENABLE_MATERIAL_U_POPUP", true, "Switch popup UX to use material U");
|
||||
|
||||
public static final BooleanFlag ENABLE_SEARCH_UNINSTALLED_APPS = getReleaseFlag(270395269,
|
||||
"ENABLE_SEARCH_UNINSTALLED_APPS", false, "Search uninstalled app results.");
|
||||
|
||||
@@ -22,8 +22,10 @@ import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
|
||||
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
|
||||
import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -56,7 +58,6 @@ import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.uioverrides.ApiWrapper;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.LogConfig;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.util.Arrays;
|
||||
@@ -368,12 +369,11 @@ public class RestoreDbTask {
|
||||
private void restoreAppWidgetIdsIfExists(Context context) {
|
||||
LauncherPrefs lp = LauncherPrefs.get(context);
|
||||
if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
|
||||
LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
|
||||
AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
|
||||
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
|
||||
IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
|
||||
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
|
||||
holder);
|
||||
holder.destroy();
|
||||
host);
|
||||
} else {
|
||||
FileLog.d(TAG, "No app widget ids to restore.");
|
||||
}
|
||||
|
||||
@@ -16,13 +16,20 @@
|
||||
|
||||
package com.android.launcher3.search;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.launcher3.util.IntArray;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Utilities for matching query string to target string.
|
||||
*/
|
||||
public class StringMatcherUtility {
|
||||
|
||||
private static final Character SPACE = ' ';
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@code query} is a prefix of a substring in {@code target}. How to
|
||||
* break target to valid substring is defined in the given {@code matcher}.
|
||||
@@ -58,6 +65,41 @@ public class StringMatcherUtility {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of breakpoints wherever the string contains a break. For example:
|
||||
* "t-mobile" would have breakpoints at [0, 1]
|
||||
* "Agar.io" would have breakpoints at [3, 4]
|
||||
* "LEGO®Builder" would have a breakpoint at [4]
|
||||
*/
|
||||
public static IntArray getListOfBreakpoints(CharSequence input, StringMatcher matcher) {
|
||||
int inputLength = input.length();
|
||||
if ((inputLength <= 2) || TextUtils.indexOf(input, SPACE) != -1) {
|
||||
// when there is a space in the string, return a list where the elements are the
|
||||
// position of the spaces - 1. This is to make the logic consistent where breakpoints
|
||||
// are placed
|
||||
return IntArray.wrap(IntStream.range(0, inputLength)
|
||||
.filter(i -> input.charAt(i) == SPACE)
|
||||
.map(i -> i - 1)
|
||||
.toArray());
|
||||
}
|
||||
IntArray listOfBreakPoints = new IntArray();
|
||||
int prevType;
|
||||
int thisType = Character.getType(Character.codePointAt(input, 0));
|
||||
int nextType = Character.getType(Character.codePointAt(input, 1));
|
||||
for (int i = 1; i < inputLength; i++) {
|
||||
prevType = thisType;
|
||||
thisType = nextType;
|
||||
nextType = i < (inputLength - 1)
|
||||
? Character.getType(Character.codePointAt(input, i + 1))
|
||||
: Character.UNASSIGNED;
|
||||
if (matcher.isBreak(thisType, prevType, nextType)) {
|
||||
// breakpoint is at previous
|
||||
listOfBreakPoints.add(i-1);
|
||||
}
|
||||
}
|
||||
return listOfBreakPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs locale sensitive string comparison using {@link Collator}.
|
||||
*/
|
||||
@@ -118,7 +160,11 @@ public class StringMatcherUtility {
|
||||
}
|
||||
switch (thisType) {
|
||||
case Character.UPPERCASE_LETTER:
|
||||
if (nextType == Character.UPPERCASE_LETTER) {
|
||||
// takes care of the case where there are consistent uppercase letters as well
|
||||
// as a special symbol following the capitalize letters for example: LEGO®
|
||||
if (nextType != Character.UPPERCASE_LETTER && nextType != Character.OTHER_SYMBOL
|
||||
&& nextType != Character.DECIMAL_DIGIT_NUMBER
|
||||
&& nextType != Character.UNASSIGNED) {
|
||||
return true;
|
||||
}
|
||||
// Follow through
|
||||
|
||||
@@ -582,8 +582,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
|
||||
? splitInfo.dividerHeightPercent
|
||||
: splitInfo.dividerWidthPercent;
|
||||
|
||||
int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize;
|
||||
float scale = (float) outRect.height() / deviceHeightWithoutTaskbar;
|
||||
float scale = (float) outRect.height() / dp.availableHeightPx;
|
||||
float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
|
||||
float scaledTopTaskHeight = topTaskHeight * scale;
|
||||
float dividerHeight = dp.availableHeightPx * dividerBarPercent;
|
||||
@@ -635,9 +634,11 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
|
||||
primarySnapshot.setTranslationX(0);
|
||||
}
|
||||
secondarySnapshot.setTranslationY(spaceAboveSnapshot);
|
||||
|
||||
// Reset unused translations
|
||||
primarySnapshot.setTranslationY(0);
|
||||
} else {
|
||||
int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize;
|
||||
float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar;
|
||||
float scale = (float) totalThumbnailHeight / dp.availableHeightPx;
|
||||
float topTaskHeight = dp.availableHeightPx * taskPercent;
|
||||
float finalDividerHeight = dividerBar * scale;
|
||||
float scaledTopTaskHeight = topTaskHeight * scale;
|
||||
@@ -669,6 +670,10 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
|
||||
View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
|
||||
View.MeasureSpec.EXACTLY));
|
||||
primarySnapshot.setScaleX(1);
|
||||
secondarySnapshot.setScaleX(1);
|
||||
primarySnapshot.setScaleY(1);
|
||||
secondarySnapshot.setScaleY(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -699,13 +704,13 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
|
||||
: deviceProfile.getInsets().left;
|
||||
int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
|
||||
- fullscreenInsetThickness) / 2);
|
||||
float midpointFromBottomPct = (float) fullscreenMidpointFromBottom
|
||||
float midpointFromEndPct = (float) fullscreenMidpointFromBottom
|
||||
/ deviceProfile.widthPx;
|
||||
float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
|
||||
int spaceAboveSnapshots = 0;
|
||||
int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
|
||||
int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
|
||||
* midpointFromBottomPct);
|
||||
* midpointFromEndPct);
|
||||
int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
|
||||
|
||||
if (deviceProfile.isSeascape()) {
|
||||
|
||||
@@ -236,8 +236,8 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
|
||||
/** Return extra space revealed during predictive back animation. */
|
||||
@Px
|
||||
protected int getBottomOffsetPx() {
|
||||
return (int) (getMeasuredHeight()
|
||||
* (1 - PREDICTIVE_BACK_MIN_SCALE) / 2);
|
||||
final int height = getMeasuredHeight();
|
||||
return (int) ((height / PREDICTIVE_BACK_MIN_SCALE - height) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -321,13 +321,6 @@ public class LauncherWidgetHolder {
|
||||
mFlags &= ~FLAG_LISTENING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the host
|
||||
*/
|
||||
public void deleteHost() {
|
||||
mWidgetHost.deleteHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The app widget ids
|
||||
*/
|
||||
|
||||
@@ -107,6 +107,7 @@ public final class TestProtocol {
|
||||
public static final String REQUEST_CLEAR_DATA = "clear-data";
|
||||
public static final String REQUEST_USE_TEST_WORKSPACE_LAYOUT = "use-test-workspace-layout";
|
||||
public static final String REQUEST_USE_TEST2_WORKSPACE_LAYOUT = "use-test2-workspace-layout";
|
||||
public static final String REQUEST_USE_TAPL_WORKSPACE_LAYOUT = "use-tapl-workspace-layout";
|
||||
public static final String REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT =
|
||||
"use-default-workspace-layout";
|
||||
public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package com.android.launcher3.search;
|
||||
|
||||
import static com.android.launcher3.search.StringMatcherUtility.getListOfBreakpoints;
|
||||
import static com.android.launcher3.search.StringMatcherUtility.matches;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -25,6 +27,7 @@ import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
|
||||
import com.android.launcher3.search.StringMatcherUtility.StringMatcherSpace;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -138,4 +141,96 @@ public class StringMatcherUtilityTest {
|
||||
assertFalse(matches("phant", "elephant", MATCHER_SPACE));
|
||||
assertFalse(matches("elephants", "elephant", MATCHER_SPACE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringWithProperBreaks() {
|
||||
// empty string
|
||||
assertEquals(IntArray.wrap(), getListOfBreakpoints("", MATCHER));
|
||||
|
||||
// should be "D Dz" that's why breakpoint is at 0
|
||||
assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDz", MATCHER));
|
||||
|
||||
// test all caps and all lower-case
|
||||
assertEquals(IntArray.wrap(), getListOfBreakpoints("SNKRS", MATCHER));
|
||||
assertEquals(IntArray.wrap(), getListOfBreakpoints("flutterappflorafy", MATCHER));
|
||||
assertEquals(IntArray.wrap(), getListOfBreakpoints("LEGO®", MATCHER));
|
||||
|
||||
// test camel case
|
||||
// breakpoint at 9 to be "flutterapp Florafy"
|
||||
assertEquals(IntArray.wrap(9), getListOfBreakpoints("flutterappFlorafy", MATCHER));
|
||||
// breakpoint at 4 to be "Metro Zone"
|
||||
assertEquals(IntArray.wrap(4), getListOfBreakpoints("MetroZone", MATCHER));
|
||||
// breakpoint at 4,5 to be "metro X Zone"
|
||||
assertEquals(IntArray.wrap(4,5), getListOfBreakpoints("metroXZone", MATCHER));
|
||||
// breakpoint at 0 to be "G Pay"
|
||||
assertEquals(IntArray.wrap(0), getListOfBreakpoints("GPay", MATCHER));
|
||||
// breakpoint at 4 to be "Whats App"
|
||||
assertEquals(IntArray.wrap(4), getListOfBreakpoints("WhatsApp", MATCHER));
|
||||
// breakpoint at 2 to be "aaa A"
|
||||
assertEquals(IntArray.wrap(2), getListOfBreakpoints("aaaA", MATCHER));
|
||||
// breakpoint at 4,12,16 to be "Other Launcher Test App"
|
||||
assertEquals(IntArray.wrap(4,12,16),
|
||||
getListOfBreakpoints("OtherLauncherTestApp", MATCHER));
|
||||
|
||||
// test with TITLECASE_LETTER
|
||||
// should be "DDz" that's why there are no break points
|
||||
assertEquals(IntArray.wrap(), getListOfBreakpoints("DDz", MATCHER));
|
||||
// breakpoint at 0 to be "D DDž"
|
||||
assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDDž", MATCHER));
|
||||
// breakpoint at 0 because there is a space to be "Dž DD"
|
||||
assertEquals(IntArray.wrap(0), getListOfBreakpoints("Dž DD", MATCHER));
|
||||
// breakpoint at 1 to be "Dw Dz"
|
||||
assertEquals(IntArray.wrap(1), getListOfBreakpoints("DwDz", MATCHER));
|
||||
// breakpoint at 0,2 to be "Dw Dz"
|
||||
assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("wDwDz", MATCHER));
|
||||
// breakpoint at 1,3 to be "ᾋw Dw Dz"
|
||||
assertEquals(IntArray.wrap(1,3), getListOfBreakpoints("ᾋwDwDz", MATCHER));
|
||||
// breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz"
|
||||
assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz", MATCHER));
|
||||
// breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz®"
|
||||
assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz®", MATCHER));
|
||||
|
||||
// test with numbers and symbols
|
||||
// breakpoint at 3,11 to be "Test Activity 13"
|
||||
assertEquals(IntArray.wrap(3,11), getListOfBreakpoints("TestActivity13", MATCHER));
|
||||
// breakpoint at 3, 4, 12, 13 as the breakpoints are at the dashes
|
||||
assertEquals(IntArray.wrap(3,4,12,13),
|
||||
getListOfBreakpoints("Test-Activity-12", MATCHER));
|
||||
// breakpoint at 1 to be "AA 2"
|
||||
assertEquals(IntArray.wrap(1), getListOfBreakpoints("AA2", MATCHER));
|
||||
// breakpoint at 1 to be "AAA 2"
|
||||
assertEquals(IntArray.wrap(2), getListOfBreakpoints("AAA2", MATCHER));
|
||||
// breakpoint at 1 to be "ab 2"
|
||||
assertEquals(IntArray.wrap(1), getListOfBreakpoints("ab2", MATCHER));
|
||||
// breakpoint at 1,2 to be "el 3 suhwee"
|
||||
assertEquals(IntArray.wrap(1,2), getListOfBreakpoints("el3suhwee", MATCHER));
|
||||
// breakpoint at 0,1 as the breakpoints are at '-'
|
||||
assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-mobile", MATCHER));
|
||||
assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-Mobile", MATCHER));
|
||||
// breakpoint at 0,1,2 as the breakpoints are at '-'
|
||||
assertEquals(IntArray.wrap(0,1,2), getListOfBreakpoints("t--Mobile", MATCHER));
|
||||
// breakpoint at 1,2,3 as the breakpoints are at '-'
|
||||
assertEquals(IntArray.wrap(1,2,3), getListOfBreakpoints("tr--Mobile", MATCHER));
|
||||
// breakpoint at 3,4 as the breakpoints are at '.'
|
||||
assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Agar.io", MATCHER));
|
||||
assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Hole.Io", MATCHER));
|
||||
|
||||
// breakpoint at 0 to be "µ Torrent®"
|
||||
assertEquals(IntArray.wrap(0), getListOfBreakpoints("µTorrent®", MATCHER));
|
||||
// breakpoint at 4 to be "LEGO® Builder"
|
||||
assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®Builder", MATCHER));
|
||||
// breakpoint at 4 to be "LEGO® builder"
|
||||
assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®builder", MATCHER));
|
||||
// breakpoint at 4 to be "lego® builder"
|
||||
assertEquals(IntArray.wrap(4), getListOfBreakpoints("lego®builder", MATCHER));
|
||||
|
||||
// test string with spaces - where the breakpoints are right before where the spaces are at
|
||||
assertEquals(IntArray.wrap(3,8), getListOfBreakpoints("HEAD BALL 2", MATCHER));
|
||||
assertEquals(IntArray.wrap(2,8),
|
||||
getListOfBreakpoints("OFL Agent Application", MATCHER));
|
||||
assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("D D z", MATCHER));
|
||||
assertEquals(IntArray.wrap(6), getListOfBreakpoints("Battery Stats", MATCHER));
|
||||
assertEquals(IntArray.wrap(5,9,15),
|
||||
getListOfBreakpoints("System UWB Field Test", MATCHER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.ui;
|
||||
|
||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.search.StringMatcherUtility;
|
||||
import com.android.launcher3.util.ActivityContextWrapper;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.TestUtil;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
|
||||
* This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
|
||||
* Verifying with getMaxLines() is sufficient since BubbleTextView can only be in one line or
|
||||
* two lines, and this is enough to ensure whether the string should be specifically wrapped onto
|
||||
* the second line and to ensure truncation.
|
||||
*/
|
||||
public class BubbleTextViewTest {
|
||||
|
||||
private static final StringMatcherUtility.StringMatcher
|
||||
MATCHER = StringMatcherUtility.StringMatcher.getInstance();
|
||||
private static final int ONE_LINE = 1;
|
||||
private static final int TWO_LINE = 2;
|
||||
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
|
||||
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
|
||||
"Battery\nStats";
|
||||
private static final String TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT =
|
||||
"flutterappflorafy";
|
||||
private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT =
|
||||
"System UWB Field Test";
|
||||
private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
|
||||
"System\nUWB Field Test";
|
||||
private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT =
|
||||
"LEGO®Builder";
|
||||
private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT =
|
||||
"LEGO®\nBuilder";
|
||||
private static final String EMPTY_STRING = "";
|
||||
private static final int CHAR_CNT = 7;
|
||||
|
||||
private BubbleTextView mBubbleTextView;
|
||||
private ItemInfoWithIcon mItemInfoWithIcon;
|
||||
private Context mContext;
|
||||
private int mLimitedWidth;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Utilities.enableRunningInTestHarnessForTests();
|
||||
mContext = new ActivityContextWrapper(getApplicationContext());
|
||||
mBubbleTextView = new BubbleTextView(mContext);
|
||||
mBubbleTextView.reset();
|
||||
mBubbleTextView.setDisplayAllApps();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
|
||||
|
||||
BubbleTextView testView = new BubbleTextView(mContext);
|
||||
testView.setTypeface(Typeface.MONOSPACE);
|
||||
testView.setText("B");
|
||||
// calculate the maxWidth of the textView by calculating the width of one monospace
|
||||
// character * CHAR_CNT
|
||||
mLimitedWidth =
|
||||
(int) (testView.getPaint().measureText(testView.getText().toString()) * CHAR_CNT);
|
||||
// needed otherwise there is a NPE during setText() on checkForRelayout()
|
||||
mBubbleTextView.setLayoutParams(
|
||||
new ViewGroup.LayoutParams(mLimitedWidth,
|
||||
BaseDragLayer.LayoutParams.WRAP_CONTENT));
|
||||
mItemInfoWithIcon = new ItemInfoWithIcon() {
|
||||
@Override
|
||||
public ItemInfoWithIcon clone() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyString_flagOn() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
|
||||
mItemInfoWithIcon.title = EMPTY_STRING;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyString_flagOff() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
|
||||
mItemInfoWithIcon.title = EMPTY_STRING;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringWithSpaceLongerThanCharLimit_flagOn() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
|
||||
// test string: "Battery Stats"
|
||||
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringWithSpaceLongerThanCharLimit_flagOff() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
|
||||
// test string: "Battery Stats"
|
||||
mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
|
||||
// test string: "flutterappflorafy"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
|
||||
// test string: "flutterappflorafy"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
|
||||
// test string: "System UWB Field Test"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
|
||||
// test string: "System UWB Field Test"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringSymbolLongerThanCharLimit_flagOn() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
|
||||
// test string: "LEGO®Builder"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongStringSymbolLongerThanCharLimit_flagOff() {
|
||||
TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
|
||||
// test string: "LEGO®Builder"
|
||||
mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
|
||||
mBubbleTextView.applyLabel(mItemInfoWithIcon);
|
||||
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
|
||||
mBubbleTextView.measure(mLimitedWidth, 0);
|
||||
mBubbleTextView.onPreDraw();
|
||||
assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyTitleToSupportMultiLine_TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
|
||||
// test string: "Battery Stats"
|
||||
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
|
||||
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
|
||||
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
|
||||
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
|
||||
breakPoints);
|
||||
assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT() {
|
||||
// test string: "flutterappflorafy"
|
||||
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
|
||||
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
|
||||
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
|
||||
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
|
||||
breakPoints);
|
||||
assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
|
||||
// test string: "System UWB Field Test"
|
||||
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
|
||||
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
|
||||
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
|
||||
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
|
||||
breakPoints);
|
||||
assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT() {
|
||||
// test string: "LEGO®Builder"
|
||||
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
|
||||
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER);
|
||||
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
|
||||
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
|
||||
breakPoints);
|
||||
assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
|
||||
}
|
||||
}
|
||||
@@ -231,6 +231,10 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
|
||||
@Test
|
||||
@ScreenRecord // b/202433017
|
||||
public void testWorkspace() throws Exception {
|
||||
// Make sure there is an instance of chrome on the hotseat
|
||||
mLauncher.useTaplWorkspaceLayoutOnReload();
|
||||
clearLauncherData();
|
||||
|
||||
final Workspace workspace = mLauncher.getWorkspace();
|
||||
|
||||
// Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
|
||||
|
||||
@@ -1865,6 +1865,15 @@ public final class LauncherInstrumentation {
|
||||
getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reloads the workspace with a test layout that includes the chrome Activity app icon on the
|
||||
* hotseat.
|
||||
*/
|
||||
public void useTaplWorkspaceLayoutOnReload() {
|
||||
getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT);
|
||||
}
|
||||
|
||||
/** Reloads the workspace with the default layout defined by the user's grid size selection. */
|
||||
public void useDefaultWorkspaceLayoutOnReload() {
|
||||
getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT);
|
||||
|
||||
Reference in New Issue
Block a user