Merge "Add debug logs for WorkTabExists test" into tm-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index a645e58..cf58198 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -221,6 +221,12 @@
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;
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index c638ba9..b1064f7 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -128,10 +128,17 @@
// Bit encoded value to capture pinned and predicted taskbar positions.
optional int32 cardinality = 2;
+
+ // Container where taskbar was invoked.
+ oneof ParentContainer {
+ TaskSwitcherContainer task_switcher_container = 3;
+ }
}
// Next value 44
enum Attribute {
+ option allow_alias = true;
+
UNKNOWN = 0;
DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
BACKUP_RESTORE = 2; // icon layout restored from backup
@@ -166,7 +173,8 @@
ALL_APPS_SEARCH_RESULT_SLICE = 19;
ALL_APPS_SEARCH_RESULT_WIDGETS = 20;
ALL_APPS_SEARCH_RESULT_PLAY = 21;
- ALL_APPS_SEARCH_RESULT_SUGGEST = 22;
+ ALL_APPS_SEARCH_RESULT_FALLBACK = 22;
+ ALL_APPS_SEARCH_RESULT_SUGGEST = 22 [deprecated = true];
ALL_APPS_SEARCH_RESULT_ASSISTANT = 23;
ALL_APPS_SEARCH_RESULT_CHROMETAB = 24;
ALL_APPS_SEARCH_RESULT_NAVVYSITE = 25;
diff --git a/quickstep/res/drawable/ic_desktop.xml b/quickstep/res/drawable/ic_desktop.xml
index dfaf8b8..8de275d 100644
--- a/quickstep/res/drawable/ic_desktop.xml
+++ b/quickstep/res/drawable/ic_desktop.xml
@@ -25,7 +25,7 @@
android:translateX="6.0"
android:translateY="6.0">
<path
- android:fillColor="?android:attr/textColorPrimary"
+ android:fillColor="@android:color/black"
android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
</group>
</vector>
diff --git a/quickstep/res/drawable/ic_empty_desktop.xml b/quickstep/res/drawable/ic_empty_desktop.xml
deleted file mode 100644
index cbf1856..0000000
--- a/quickstep/res/drawable/ic_empty_desktop.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="92dp"
- android:height="80dp"
- android:tint="?android:attr/textColorPrimary"
- android:viewportHeight="80.0"
- android:viewportWidth="92.0">
- <path
- android:fillColor="#AAFFFFFF"
- android:pathData="M 14.365954,80 Q 10.981668,80 8.4908345,77.509166 6,75.018332 6,71.634046 V 36.193807 q 0,-3.384286 2.4908345,-5.87512 2.4908335,-2.493091 5.8751195,-2.493091 H 22.35738 V 8.365954 q 0,-3.3842855 2.538217,-5.8751198 Q 27.433811,0 30.723337,0 h 46.910711 q 3.479041,0 5.969878,2.4908342 2.490834,2.4908343 2.490834,5.8751198 v 35.442495 q 0,3.384286 -2.490834,5.87512 -2.490837,2.490835 -5.969878,2.490835 h -7.896671 v 19.459642 q 0,3.384286 -2.49083,5.87512 Q 64.755713,80 61.371423,80 Z m 0,-8.365954 h 47.005469 q 0,0 0,0 0,0 0,0 V 43.526426 H 14.365954 v 28.10762 q 0,0 0,0 0,0 0,0 z M 69.737377,43.808449 h 7.896671 q 0,0 0,0 0,0 0,0 V 15.698573 H 30.723337 v 12.127023 h 30.740592 q 3.479048,0 5.877376,2.445711 2.396072,2.443454 2.396072,5.82774 z" />
-</vector>
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index f454835..2ec9d4c 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -32,19 +32,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <TextView
- android:id="@+id/empty_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="@dimen/overview_task_margin"
- android:drawablePadding="@dimen/recents_empty_message_text_padding"
- android:text="@string/recents_empty_desktop_message"
- android:textColor="?android:textColorPrimary"
- android:textSize="@dimen/recents_empty_message_text_size"
- android:drawableTop="@drawable/ic_empty_desktop"
- android:drawableTint="?android:attr/textColorPrimary" />
-
<!--
TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
and IconView with these ids to be present. Need to refactor RecentsView to accept child
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw600dp/config.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp-land/config.xml b/quickstep/res/values-sw720dp-land/config.xml
new file mode 100644
index 0000000..bf0f9ad
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/config.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp/config.xml b/quickstep/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/config.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d581582..b61bdfb 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -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>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 2b5975d..eb5fed2 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,9 +30,6 @@
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
- <!-- Recents: The empty recents desktop tile string. [CHAR LIMIT=NONE] -->
- <string name="recents_empty_desktop_message">No desktop items</string>
-
<!-- Content description for the recent apps's accessibility option that opens its usage settings. [CHAR LIMIT=NONE] -->
<string name="accessibility_app_usage_settings">App usage settings</string>
@@ -278,6 +275,10 @@
<string name="taskbar_button_quick_settings">Quick Settings</string>
<!-- Accessibility title for the Taskbar window. [CHAR_LIMIT=NONE] -->
<string name="taskbar_a11y_title">Taskbar</string>
+ <!-- Accessibility title for the Taskbar window appeared. [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_a11y_shown_title">Taskbar shown</string>
+ <!-- Accessibility title for the Taskbar window being close. [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
<!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
<string name="taskbar_phone_a11y_title">Navigation bar</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5ddf2a8..da28cfa 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -52,6 +52,7 @@
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -122,7 +123,6 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.RunnableList;
@@ -448,7 +448,7 @@
}
if (mDeviceProfile.isTaskbarPresentInApps
&& !target.willShowImeOnTarget
- && !DisplayController.isTransientTaskbar(mLauncher)) {
+ && !isTransientTaskbar(mLauncher)) {
// Animate to above the taskbar.
bounds.bottom -= target.contentInsets.bottom;
}
@@ -634,7 +634,10 @@
boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets);
RectF launcherIconBounds = new RectF();
- FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
+ FloatingIconView floatingView = getFloatingIconView(mLauncher, v,
+ (mLauncher.getTaskbarUIController() == null || !isTransientTaskbar(mLauncher))
+ ? null
+ : mLauncher.getTaskbarUIController().findMatchingView(v),
!appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
Rect crop = new Rect();
Matrix matrix = new Matrix();
@@ -1350,6 +1353,9 @@
isTransluscent, fallbackBackgroundColor);
} else if (launcherView != null) {
floatingIconView = getFloatingIconView(mLauncher, launcherView,
+ mLauncher.getTaskbarUIController() == null
+ ? null
+ : mLauncher.getTaskbarUIController().findMatchingView(launcherView),
true /* hideOriginal */, targetRect, false /* isOpening */);
} else {
targetRect.set(getDefaultWindowTargetRect());
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 793c68e..95fea3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -341,6 +341,11 @@
}
@Override
+ protected boolean isInOverview() {
+ return mTaskbarLauncherStateController.isInOverview();
+ }
+
+ @Override
public RecentsView getRecentsView() {
return mLauncher.getOverviewPanel();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index bafd5b4..84bf02e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -288,14 +288,9 @@
updateButtonLayoutSpacing();
updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
- // Animate taskbar background when either..
- // notification shade expanded AND not on keyguard
- // back is visible for bouncer
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
- flags -> ((flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0
- && (flags & FLAG_KEYGUARD_VISIBLE) == 0)
- || (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+ flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
// Rotation button
RotationButton rotationButton = new RotationButtonImpl(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2864ac7..62713ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -316,7 +316,6 @@
return mTransientTaskbarBounds;
}
- @VisibleForTesting
@Override
public StatsLogManager getStatsLogManager() {
// Used to mock, can't mock a default interface method directly
@@ -432,11 +431,16 @@
}
LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
+ LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+ LauncherAtom.TaskBarContainer.newBuilder();
+ if (mControllers.uiController.isInOverview()) {
+ taskbarBuilder.setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.newBuilder());
+ }
+
if (oldContainer.hasPredictedHotseatContainer()) {
LauncherAtom.PredictedHotseatContainer predictedHotseat =
oldContainer.getPredictedHotseatContainer();
- LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
- LauncherAtom.TaskBarContainer.newBuilder();
if (predictedHotseat.hasIndex()) {
taskbarBuilder.setIndex(predictedHotseat.getIndex());
@@ -449,8 +453,6 @@
.setTaskBarContainer(taskbarBuilder));
} else if (oldContainer.hasHotseat()) {
LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
- LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
- LauncherAtom.TaskBarContainer.newBuilder();
if (hotseat.hasIndex()) {
taskbarBuilder.setIndex(hotseat.getIndex());
@@ -462,8 +464,6 @@
LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
.toBuilder();
LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
- LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
- LauncherAtom.TaskBarContainer.newBuilder();
if (hotseat.hasIndex()) {
taskbarBuilder.setIndex(hotseat.getIndex());
@@ -476,11 +476,11 @@
} else if (oldContainer.hasAllAppsContainer()) {
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
.setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder()
- .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+ .setTaskbarContainer(taskbarBuilder)));
} else if (oldContainer.hasPredictionContainer()) {
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
.setPredictionContainer(oldContainer.getPredictionContainer().toBuilder()
- .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+ .setTaskbarContainer(taskbarBuilder)));
}
}
@@ -588,10 +588,8 @@
AnimatorSet anim = new AnimatorSet();
anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(
TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
- if (!isThreeButtonNav()) {
- anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
- .animateToValue(alpha));
- }
+ anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
+ .animateToValue(alpha));
anim.start();
if (skipAnim) {
anim.end();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 5ac0570..4d163aa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -406,6 +406,10 @@
return mLauncherState != LauncherState.ALL_APPS;
}
+ boolean isInOverview() {
+ return mLauncherState == LauncherState.OVERVIEW;
+ }
+
private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
boolean committed) {
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index babafd5..e0acd3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -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;
@@ -24,6 +25,8 @@
import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
import static com.android.launcher3.taskbar.Utilities.appendFlag;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -43,6 +46,7 @@
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 +193,7 @@
// 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 +226,7 @@
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();
@@ -600,6 +606,7 @@
if (!mIsStashed) {
tryStartTaskbarTimeout();
}
+ mControllers.taskbarViewController.announceForAccessibility();
}
});
}
@@ -722,8 +729,8 @@
skipInterpolator = FINAL_FRAME;
}
}
- play(as, mControllers.taskbarViewController
- .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
+ mControllers.taskbarViewController.addRevealAnimToIsStashed(as, isStashed, duration,
+ EMPHASIZED);
if (skipInterpolator != null) {
as.setInterpolator(skipInterpolator);
@@ -967,6 +974,11 @@
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
}
}
+ if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
+ mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
+ ? LAUNCHER_TRANSIENT_TASKBAR_HIDE
+ : LAUNCHER_TRANSIENT_TASKBAR_SHOW);
+ }
}
private void notifyStashChange(boolean visible, boolean stashed) {
@@ -1017,7 +1029,15 @@
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) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index b552e9b..4c6d3fa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -17,6 +17,8 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import android.content.Intent;
@@ -166,6 +168,11 @@
return true;
}
+ /** Returns {@code true} if Taskbar is currently within overview. */
+ protected boolean isInOverview() {
+ return false;
+ }
+
@CallSuper
protected void dumpLogs(String prefix, PrintWriter pw) {
pw.println(String.format(
@@ -277,4 +284,30 @@
* No-op if the view is not yet open.
*/
public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
+
+ /**
+ * Returns the matching view (if any) in the taskbar.
+ * @param view The view to match.
+ */
+ public @Nullable View findMatchingView(View view) {
+ if (!(view.getTag() instanceof ItemInfo)) {
+ return null;
+ }
+ ItemInfo info = (ItemInfo) view.getTag();
+ if (info.container != CONTAINER_HOTSEAT && info.container != CONTAINER_HOTSEAT_PREDICTION) {
+ return null;
+ }
+
+ // Taskbar has the same items as the hotseat and we can use screenId to find the match.
+ int screenId = info.screenId;
+ View[] views = mControllers.taskbarViewController.getIconViews();
+ for (int i = views.length - 1; i >= 0; --i) {
+ if (views[i] != null
+ && views[i].getTag() instanceof ItemInfo
+ && ((ItemInfo) views[i].getTag()).screenId == screenId) {
+ return views[i];
+ }
+ }
+ return null;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 3d5089f..e66856a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,15 +16,18 @@
package com.android.launcher3.taskbar;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
@@ -91,6 +94,8 @@
private float mTransientTaskbarAllAppsButtonTranslationXOffset;
+ private final boolean mStartAlignTaskbar;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -118,6 +123,8 @@
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;
@@ -153,7 +160,26 @@
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+ }
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+ announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
+ } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
+ announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
+ }
+ return super.performAccessibilityActionInternal(action, arguments);
+
+ }
+
+ protected void announceAccessibilityChanges() {
+ this.performAccessibilityAction(
+ isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+ : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+
+ ActivityContext.lookupContext(getContext()).getDragLayer()
+ .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
}
private int getColorWithGivenLuminance(int color, float luminance) {
@@ -163,6 +189,13 @@
return ColorUtils.HSLToColor(colorHSL);
}
+ /**
+ * Returns the icon touch size.
+ */
+ public int getIconTouchSize() {
+ return mIconTouchSize;
+ }
+
private int calculateThemeIconsBackground() {
int color = ThemedIconDrawable.getColors(mContext)[0];
if (Utilities.isDarkTheme(mContext)) {
@@ -343,10 +376,22 @@
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;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3143f23..50dbcc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -63,7 +63,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.HorizontalInsettableView;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
@@ -91,6 +90,9 @@
public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
private static final int NUM_ALPHA_CHANNELS = 7;
+ // This allows the icons on the edge to stay within the taskbar background bounds.
+ private static final float ICON_REVEAL_X_DURATION_MULTIPLIER = 0.8f;
+
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
private final MultiValueAlpha mTaskbarIconAlpha;
@@ -189,6 +191,13 @@
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
}
+ /**
+ * Announcement for Accessibility when Taskbar stashes/unstashes.
+ */
+ public void announceForAccessibility() {
+ mTaskbarView.announceAccessibilityChanges();
+ }
+
public void onDestroy() {
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
@@ -304,73 +313,87 @@
/**
* Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape
* and size.
+ * @param as The AnimatorSet to add all animations to.
* @param isStashed When true, the icon crops vertically to the size of the stashed handle.
* When false, the reverse happens.
+ * @param duration The duration of the animation.
+ * @param interpolator The interpolator to use for all animations.
*/
- public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
- AnimatorSet as = new AnimatorSet();
+ public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+ Interpolator interpolator) {
+ AnimatorSet reveal = new AnimatorSet();
Rect stashedBounds = new Rect();
mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds);
- int numChildren = mTaskbarView.getChildCount();
+ boolean isQsbInline = mActivity.getDeviceProfile().isQsbInline;
+ int numIcons = mTaskbarView.getChildCount() - (isQsbInline ? 1 : 0);
// We do not actually modify the width of the icons, but we will use this width to position
// the children to overlay the nav handle.
- float virtualChildWidth = stashedBounds.width() / (float) numChildren;
+ float virtualChildWidth = stashedBounds.width() / (float) numIcons;
- for (int i = numChildren - 1; i >= 0; i--) {
+ // All children move the same y-amount since they will be cropped to the same centerY.
+ float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height();
+
+ boolean isRtl = Utilities.isRtl(mTaskbarView.getResources());
+ for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) {
View child = mTaskbarView.getChildAt(i);
-
- if (child == mTaskbarView.getQsb()) {
- continue;
- }
+ boolean isQsb = child == mTaskbarView.getQsb();
// Crop the icons to/from the nav handle shape.
- as.play(createRevealAnimForView(child, isStashed));
+ reveal.play(createRevealAnimForView(child, isStashed).setDuration(duration));
// Translate the icons to/from their locations as the "nav handle."
// We look at 'left' and 'right' values to ensure that the children stay within the
// bounds of the stashed handle.
- float iconLeft = child.getLeft();
- float newLeft = stashedBounds.left + (virtualChildWidth * i);
+
+ // All of the taskbar icons will overlap the entirety of the stashed handle
+ // And the QSB, if inline, will overlap part of stashed handle as well.
+ int positionInHandle = (isQsbInline && !isQsb)
+ ? i + (isRtl ? 1 : -1)
+ : i;
+ float currentPosition = isQsb ? child.getX() : child.getLeft();
+ float newPosition = stashedBounds.left + (virtualChildWidth * positionInHandle);
final float croppedTransX;
- if (iconLeft > newLeft) {
- float newRight = stashedBounds.right - (virtualChildWidth * (numChildren - 1 - i));
- croppedTransX = -(child.getLeft() + child.getWidth() - newRight);
+ if (currentPosition > newPosition) {
+ float newRight = stashedBounds.right - (virtualChildWidth
+ * (numIcons - 1 - positionInHandle));
+ croppedTransX = -(currentPosition + child.getWidth() - newRight);
} else {
- croppedTransX = newLeft - iconLeft;
+ croppedTransX = newPosition - currentPosition;
}
- float croppedTransY = child.getHeight() - stashedBounds.height();
+ long transXDuration = (long) (duration * ICON_REVEAL_X_DURATION_MULTIPLIER);
+ float[] transX = isStashed
+ ? new float[] {croppedTransX}
+ : new float[] {croppedTransX, 0};
+ float[] transY = isStashed
+ ? new float[] {croppedTransY}
+ : new float[] {croppedTransY, 0};
+
if (child instanceof Reorderable) {
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
- as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
- MULTI_PROPERTY_VALUE, isStashed
- ? new float[] {croppedTransX}
- : new float[] {croppedTransX, 0}));
- as.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
- MULTI_PROPERTY_VALUE, isStashed
- ? new float[] {croppedTransY}
- : new float[] {croppedTransY, 0}));
+ reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, transX)
+ .setDuration(transXDuration));
+ reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, transY));
as.addListener(forEndCallback(() ->
mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
} else {
- as.play(ObjectAnimator.ofFloat(child,
- VIEW_TRANSLATE_X, isStashed
- ? new float[] {croppedTransX}
- : new float[] {croppedTransX, 0}));
- as.play(ObjectAnimator.ofFloat(child,
- VIEW_TRANSLATE_Y, isStashed
- ? new float[] {croppedTransY}
- : new float[] {croppedTransY, 0}));
+ reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX)
+ .setDuration(transXDuration));
+ reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY));
as.addListener(forEndCallback(() -> {
child.setTranslationX(0);
child.setTranslationY(0);
}));
}
}
- return as;
+
+ reveal.setInterpolator(interpolator);
+ as.play(reveal);
}
/**
@@ -431,7 +454,6 @@
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
- int positionInHotseat;
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
if (!mIsHotseatIconOnTopWhenAligned) {
// When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController
@@ -439,10 +461,17 @@
// to avoid icons disappearing rather than fading out visually.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
} else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())) {
- setter.setViewAlpha(child, 0,
- isToHome
- ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
- : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
+ if (!isToHome
+ && mIsHotseatIconOnTopWhenAligned
+ && mControllers.taskbarStashController.isStashed()) {
+ // Prevent All Apps icon from appearing when going from hotseat to nav handle.
+ setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
+ } else {
+ setter.setViewAlpha(child, 0,
+ isToHome
+ ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
+ : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
+ }
}
if (child == mTaskbarView.getQsb()) {
@@ -454,32 +483,36 @@
float childCenter = (child.getLeft() + child.getRight()) / 2f;
float halfQsbIconWidthDiff =
(launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f;
- setter.addFloat(child, VIEW_TRANSLATE_X,
- isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
- hotseatIconCenter - childCenter, interpolator);
-
float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
- setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff;
+ float toX = hotseatIconCenter - childCenter;
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+ setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, fromX, toX, interpolator);
+ setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
+ MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+ } else {
+ setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+ }
if (mIsHotseatIconOnTopWhenAligned) {
setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
isToHome
? Interpolators.clampToProgress(LINEAR, 0f, 0.35f)
- : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
+ : mActivity.getDeviceProfile().isQsbInline
+ ? Interpolators.clampToProgress(LINEAR, 0f, 1f)
+ : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
}
setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
-
- float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.hotseatQsbWidth;
- if (child instanceof HorizontalInsettableView) {
- setter.addFloat((HorizontalInsettableView) child,
- HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0,
- interpolator);
- }
continue;
}
+ int positionInHotseat;
if (isAllAppsButton) {
// Note that there is no All Apps button in the hotseat, this position is only used
// as its convenient for animation purposes.
@@ -496,18 +529,17 @@
float hotseatIconCenter = hotseatPadding.left
+ (hotseatCellSize + borderSpacing) * positionInHotseat
+ hotseatCellSize / 2f;
-
float childCenter = (child.getLeft() + child.getRight()) / 2f;
+ float toX = hotseatIconCenter - childCenter;
if (child instanceof Reorderable) {
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
- MULTI_PROPERTY_VALUE, hotseatIconCenter - childCenter, interpolator);
+ MULTI_PROPERTY_VALUE, toX, interpolator);
setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
} else {
- setter.setFloat(child, VIEW_TRANSLATE_X,
- hotseatIconCenter - childCenter, interpolator);
+ setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator);
setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
}
setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a53f08a..2a46e08 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,11 +20,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
-import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
-import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -40,6 +39,9 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
@@ -80,7 +82,6 @@
import android.view.HapticFeedbackConstants;
import android.view.RemoteAnimationTarget;
import android.view.View;
-import android.view.WindowManagerGlobal;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
@@ -171,6 +172,7 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
import com.android.systemui.unfold.UnfoldSharedComponent;
@@ -186,8 +188,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -385,22 +390,30 @@
@Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
- Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
- if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
- RecentsView recentsView = getOverviewPanel();
- // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
- List<SplitPositionOption> positions =
- recentsView.getPagedOrientationHandler().getSplitPositionOptions(
- mDeviceProfile);
- List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
- for (SplitPositionOption position : positions) {
- splitShortcuts.add(getSplitSelectShortcutByPosition(position));
- }
- base = Stream.concat(base, splitShortcuts.stream());
+ // Order matters as it affects order of appearance in popup container
+ List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
+ APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+ shortcuts.addAll(getSplitShortcuts());
+ shortcuts.add(WIDGETS);
+ shortcuts.add(INSTALL);
+ return shortcuts.stream();
+ }
+
+ private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
+
+ if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+ return Collections.emptyList();
}
- return Stream.concat(
- Stream.of(mHotseatPredictionController),
- Stream.concat(base, super.getSupportedShortcuts()));
+ RecentsView recentsView = getOverviewPanel();
+ // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
+ List<SplitPositionOption> positions =
+ recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+ mDeviceProfile);
+ List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
+ for (SplitPositionOption position : positions) {
+ splitShortcuts.add(getSplitSelectShortcutByPosition(position));
+ }
+ return splitShortcuts;
}
/**
@@ -408,15 +421,18 @@
*/
private void onStateOrResumeChanging(boolean inTransition) {
LauncherState state = getStateManager().getState();
- if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
- boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
- if (started) {
- DeviceProfile profile = getDeviceProfile();
- boolean willUserBeActive =
- (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
- boolean visible = (state == NORMAL || state == OVERVIEW)
- && (willUserBeActive || isUserActive())
- && !profile.isVerticalBarLayout();
+ boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
+ if (started) {
+ DeviceProfile profile = getDeviceProfile();
+ boolean willUserBeActive =
+ (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+ boolean visible = (state == NORMAL || state == OVERVIEW)
+ && (willUserBeActive || isUserActive())
+ && !profile.isVerticalBarLayout();
+ if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
+ SystemUiProxy.INSTANCE.get(this)
+ .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
+ } else {
SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
}
}
@@ -1054,8 +1070,7 @@
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
mLastTouchUpTime);
}
- if (item != null && (item.animationType == DEFAULT_NO_ICON
- || item.animationType == VIEW_BACKGROUND)) {
+ if (item != null && item.itemType == ITEM_TYPE_SEARCH_ACTION) {
activityOptions.options.setSplashScreenStyle(
SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index ff3a292..b318100 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -168,6 +168,7 @@
@Override
public void deleteAppWidgetId(int appWidgetId) {
super.deleteAppWidgetId(appWidgetId);
+ mViews.remove(appWidgetId);
sListeners.remove(appWidgetId);
}
@@ -260,6 +261,7 @@
*/
@Override
public void clearViews() {
+ mViews.clear();
for (int i = sListeners.size() - 1; i >= 0; i--) {
sListeners.valueAt(i).mListeningHolders.remove(this);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
index 177a399..d4944d0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
@@ -24,8 +24,6 @@
public final boolean defaultValue;
- boolean mHasBeenChangedAtLeastOnce;
-
public DebugFlag(String key, String description, boolean defaultValue, boolean currentValue) {
super(currentValue);
this.key = key;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index 4ca7e31..b7fb2ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -35,6 +35,9 @@
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import java.util.List;
+import java.util.Set;
+
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
*/
@@ -50,31 +53,15 @@
@Override
public void putBoolean(String key, boolean value) {
- for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
- if (flag.key.equals(key)) {
- SharedPreferences prefs = mContext.getSharedPreferences(
- FLAGS_PREF_NAME, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- // We keep the key in the prefs even if it has the default value, because it's a
- // signal that it has been changed at one point.
- if (!prefs.contains(key) && value == flag.defaultValue) {
- editor.remove(key).apply();
- flag.mHasBeenChangedAtLeastOnce = false;
- } else {
- editor.putBoolean(key, value).apply();
- flag.mHasBeenChangedAtLeastOnce = true;
- }
- updateMenu();
- }
- }
+ mSharedPreferences.edit().putBoolean(key, value).apply();
+ updateMenu();
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
if (flag.key.equals(key)) {
- return mContext.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .getBoolean(key, flag.defaultValue);
+ return mSharedPreferences.getBoolean(key, flag.defaultValue);
}
}
return defaultValue;
@@ -89,11 +76,22 @@
}
public void applyTo(PreferenceGroup parent) {
+ Set<String> modifiedPrefs = mSharedPreferences.getAll().keySet();
+ List<DebugFlag> flags = FlagsFactory.getDebugFlags();
+ flags.sort((f1, f2) -> {
+ // Sort first by any prefs that the user has changed, then alphabetically.
+ int changeComparison = Boolean.compare(
+ modifiedPrefs.contains(f2.key), modifiedPrefs.contains(f1.key));
+ return changeComparison != 0
+ ? changeComparison
+ : f1.key.compareToIgnoreCase(f2.key);
+ });
+
// For flag overrides we only want to store when the engineer chose to override the
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
- for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
+ for (DebugFlag flag : flags) {
SwitchPreference switchPreference = new SwitchPreference(mContext);
switchPreference.setKey(flag.key);
switchPreference.setDefaultValue(flag.defaultValue);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 84b873d..888cc9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -53,7 +53,6 @@
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
-
private final Set<String> mKeySet = new HashSet<>();
private boolean mRestartRequested = false;
@@ -75,7 +74,6 @@
.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
boolean currentValue = prefs.getBoolean(key, defaultValue);
DebugFlag flag = new DebugFlag(key, description, defaultValue, currentValue);
- flag.mHasBeenChangedAtLeastOnce = prefs.contains(key);
sDebugFlags.add(flag);
return flag;
} else {
@@ -96,7 +94,6 @@
boolean currentValue = prefs.getBoolean(key, defaultValue);
DebugFlag flag = new DeviceFlag(key, description, defaultValue, currentValue,
defaultValueInCode);
- flag.mHasBeenChangedAtLeastOnce = prefs.contains(key);
sDebugFlags.add(flag);
return flag;
} else {
@@ -117,19 +114,9 @@
if (!Utilities.IS_DEBUG_DEVICE) {
return Collections.emptyList();
}
- List<DebugFlag> flags;
synchronized (sDebugFlags) {
- flags = new ArrayList<>(sDebugFlags);
+ return new ArrayList<>(sDebugFlags);
}
- flags.sort((f1, f2) -> {
- // Sort first by any prefs that the user has changed, then alphabetically.
- int changeComparison = Boolean.compare(
- f2.mHasBeenChangedAtLeastOnce, f1.mHasBeenChangedAtLeastOnce);
- return changeComparison != 0
- ? changeComparison
- : f1.key.compareToIgnoreCase(f2.key);
- });
- return flags;
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d2f9294..214679a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -75,9 +75,17 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
recentsView.getTaskSize(sTempRect);
- float scale = (float) sTempRect.height() / workspacePageHeight;
+ float scale;
+ DeviceProfile deviceProfile = launcher.getDeviceProfile();
+ if (deviceProfile.isTwoPanels) {
+ // In two panel layout, width does not include both panels or space between them, so
+ // use height instead. We do not use height for handheld, as cell layout can be
+ // shorter than a task and we want the workspace to scale down to task size.
+ scale = (float) sTempRect.height() / deviceProfile.getCellLayoutHeight();
+ } else {
+ scale = (float) sTempRect.width() / deviceProfile.getCellLayoutWidth();
+ }
float parallaxFactor = 0.5f;
return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index a02f3de..c7cd39c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -32,6 +32,7 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -103,7 +104,10 @@
}
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
- config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR, 0.33f, 1));
+ config.setInterpolator(ANIM_SCRIM_FADE,
+ fromState == OVERVIEW_SPLIT_SELECT
+ ? clampToProgress(LINEAR, 0.33f, 1)
+ : LINEAR);
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
@@ -112,7 +116,10 @@
// Overview is going offscreen, so keep it at its current scale and opacity.
config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, EMPHASIZED_DECELERATE);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X,
+ fromState == OVERVIEW_SPLIT_SELECT
+ ? EMPHASIZED_DECELERATE
+ : clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
// Scroll RecentsView to page 0 as it goes offscreen, if necessary.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index df95dc1..b7bafd8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -191,7 +191,7 @@
// need to manually set the duration to a reasonable value.
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
}
- if (FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() &&
+ if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
((mFromState == NORMAL && mToState == ALL_APPS)
|| (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
mVibratorWrapper.vibrateForDragBump();
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 3e565b3..487da92 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -244,6 +244,7 @@
float maxScale = res.getFloat(R.dimen.overview_max_scale);
int taskMargin = dp.overviewTaskMarginPx;
calculateTaskSizeInternal(
+ context,
dp,
dp.overviewTaskThumbnailTopMarginPx,
dp.getOverviewActionsClaimedSpace(),
@@ -259,10 +260,10 @@
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 @@
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 @@
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 @@
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 @@
Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
}
calculateTaskSizeInternal(
+ context,
dp,
dp.overviewTaskMarginPx,
claimedSpaceBelow,
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index bb781c8..3151374 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -105,6 +105,9 @@
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
+ mActivity.getTaskbarUIController() == null
+ ? null
+ : mActivity.getTaskbarUIController().findMatchingView(workspaceView),
true /* hideOriginal */, iconLocation, false /* isOpening */);
// We want the window alpha to be 0 once this threshold is met, so that the
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 86b02aa..7c09805 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -87,6 +87,7 @@
new MainThreadInitializedObject<>(SystemUiProxy::new);
private static final int MSG_SET_SHELF_HEIGHT = 1;
+ private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2;
private ISystemUiProxy mSystemUiProxy;
private IPip mPip;
@@ -121,6 +122,10 @@
private int mLastShelfHeight;
private boolean mLastShelfVisible;
+ // Used to dedupe calls to SystemUI
+ private int mLastLauncherKeepClearAreaHeight;
+ private boolean mLastLauncherKeepClearAreaHeightVisible;
+
private final Context mContext;
private final Handler mAsyncHandler;
@@ -454,6 +459,33 @@
}
/**
+ * Sets the height of the keep clear area that is going to be reported by
+ * the Launcher for the Hotseat.
+ */
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
+ visible ? 1 : 0 , height).sendToTarget();
+ }
+
+ @WorkerThread
+ private void setLauncherKeepClearAreaHeight(int visibleInt, int height) {
+ boolean visible = visibleInt != 0;
+ boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible
+ || height != mLastLauncherKeepClearAreaHeight;
+ IPip pip = mPip;
+ if (pip != null && changed) {
+ mLastLauncherKeepClearAreaHeightVisible = visible;
+ mLastLauncherKeepClearAreaHeight = height;
+ try {
+ pip.setLauncherKeepClearAreaHeight(visible, height);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible
+ + " height: " + height, e);
+ }
+ }
+ }
+
+ /**
* Sets listener to get pip animation callbacks.
*/
public void setPipAnimationListener(IPipAnimationListener listener) {
@@ -945,6 +977,9 @@
case MSG_SET_SHELF_HEIGHT:
setShelfHeightAsync(msg.arg1, msg.arg2);
return true;
+ case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT:
+ setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2);
+ return true;
}
return false;
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 6d8ee10..6f502d0 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -302,7 +302,10 @@
@Nullable
public String getPackageName() {
- return mTopTask == null ? null : mTopTask.baseActivity.getPackageName();
+ if (mTopTask == null || mTopTask.baseActivity == null) {
+ return null;
+ }
+ return mTopTask.baseActivity.getPackageName();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index 9982162..1ddb855 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -53,6 +53,7 @@
private final float mScreenWidth;
private final int mTaskbarNavThreshold;
+ private final int mTaskbarNavThresholdY;
private final boolean mIsTaskbarAllAppsOpen;
private boolean mHasPassedTaskbarNavThreshold;
@@ -74,6 +75,8 @@
Resources res = context.getResources();
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
+ mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
+ - mTaskbarNavThreshold;
mIsTaskbarAllAppsOpen =
mTaskbarActivityContext != null && mTaskbarActivityContext.isTaskbarAllAppsOpen();
@@ -163,7 +166,7 @@
}
if (dY < 0) {
- dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThreshold);
+ dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThresholdY);
if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
mTransitionCallback.onActionMove(dY);
}
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 433d23f..b3f5d82 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -16,6 +16,8 @@
package com.android.quickstep.util;
+import androidx.annotation.NonNull;
+
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -27,9 +29,10 @@
*/
public class DesktopTask extends GroupTask {
- public ArrayList<Task> tasks;
+ @NonNull
+ public final ArrayList<Task> tasks;
- public DesktopTask(ArrayList<Task> tasks) {
+ public DesktopTask(@NonNull ArrayList<Task> tasks) {
super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
this.tasks = tasks;
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 6722f3d..c4ba39a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -396,7 +396,7 @@
* 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());
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
new file mode 100644
index 0000000..6dd67de
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -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))
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5214f7c..1f4085f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -82,6 +82,7 @@
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;
@@ -96,6 +97,11 @@
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 */
@@ -116,6 +122,7 @@
mStateManager = stateManager;
mDepthController = depthController;
mRecentTasksModel = recentsModel;
+ mSplitAnimationController = new SplitAnimationController(this);
}
/**
@@ -399,6 +406,18 @@
mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
}
+ public boolean isDismissingFromSplitPair() {
+ return mDismissingFromSplitPair;
+ }
+
+ public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
+ mDismissingFromSplitPair = dismissingFromSplitPair;
+ }
+
+ public SplitAnimationController getSplitAnimationController() {
+ return mSplitAnimationController;
+ }
+
/**
* Requires Shell Transitions
*/
@@ -506,6 +525,7 @@
mItemInfo = null;
mSplitEvent = null;
mAnimateCurrentTaskDismissal = false;
+ mDismissingFromSplitPair = false;
}
/**
@@ -532,6 +552,10 @@
return mInitialTaskId;
}
+ public int getSecondTaskId() {
+ return mSecondTaskId;
+ }
+
private boolean isSecondTaskIntentSet() {
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index f94d80f..dcbccc5 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -37,6 +37,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.icons.IconProvider;
import com.android.quickstep.TaskAnimationManager;
import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -152,7 +153,7 @@
if (SystemProperties.getBoolean(
"persist.wm.debug.enable_pip_app_icon_overlay", true)) {
mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
- mAppBounds, mActivityInfo);
+ mAppBounds, () -> new IconProvider(context).getIcon(mActivityInfo));
} else {
mPipContentOverlay = new PipContentOverlay.PipColorOverlay(view.getContext());
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 0b83eaf..0d61bc8 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -272,9 +272,7 @@
*/
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 @@
/* 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 @@
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 @@
return;
}
Log.d(TAG, "progress: " + fullScreenProgress
- + " scale: " + scale
+ " recentsViewScale: " + recentsViewScale.value
+ " crop: " + mTmpCropRect
+ " radius: " + getCurrentCornerRadius()
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 14898b1..ccc2df6 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -92,7 +92,6 @@
private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
private View mBackgroundView;
- private View mEmptyView;
public DesktopTaskView(Context context) {
this(context, null);
@@ -111,7 +110,6 @@
super.onFinishInflate();
mBackgroundView = findViewById(R.id.background);
- mEmptyView = findViewById(R.id.empty_view);
int topMarginPx =
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
@@ -187,8 +185,6 @@
mSnapshotViewMap.put(task.key.id, snapshotView);
}
- mEmptyView.setVisibility(mTasks.isEmpty() ? View.VISIBLE : View.GONE);
-
updateTaskIdContainer();
updateTaskIdAttributeContainer();
@@ -500,7 +496,7 @@
}
@Override
- void setThumbnailVisibility(int visibility) {
+ void setThumbnailVisibility(int visibility, int taskId) {
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
mSnapshotViewMap.valueAt(i).setVisibility(visibility);
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 96504af..af80d5f 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -97,11 +97,6 @@
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 @@
@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 @@
}
mBanner.setTranslationY(
- (mBannerOffsetPercentage * mBanner.getHeight()) +
- mModalOffset +
- mSplitOffsetTranslationY
- );
+ (mBannerOffsetPercentage * mBanner.getHeight()) + mSplitOffsetTranslationY);
}
private void updateTranslationX() {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index adea1a4..4ea7753 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -27,8 +27,10 @@
import android.view.ViewOutlineProvider;
import android.widget.RemoteViews.RemoteViewOutlineProvider;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.RoundedCornerEnforcement;
@@ -65,14 +67,20 @@
setClipToOutline(true);
}
- void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
- int fallbackBackgroundColor) {
+ void init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView,
+ float finalRadius, int fallbackBackgroundColor) {
mFinalRadius = finalRadius;
mSourceView = backgroundView;
mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
mIsUsingFallback = false;
if (isSupportedDrawable(backgroundView.getForeground())) {
- mOriginalForeground = backgroundView.getForeground();
+ if (backgroundView.getTag(R.id.saved_floating_widget_foreground) == null) {
+ mOriginalForeground = backgroundView.getForeground();
+ backgroundView.setTag(R.id.saved_floating_widget_foreground, mOriginalForeground);
+ } else {
+ mOriginalForeground = (Drawable) backgroundView.getTag(
+ R.id.saved_floating_widget_foreground);
+ }
mForegroundProperties.init(
mOriginalForeground.getConstantState().newDrawable().mutate());
setForeground(mForegroundProperties.mDrawable);
@@ -82,7 +90,13 @@
mSourceView.setForeground(clipPlaceholder);
}
if (isSupportedDrawable(backgroundView.getBackground())) {
- mOriginalBackground = backgroundView.getBackground();
+ if (backgroundView.getTag(R.id.saved_floating_widget_background) == null) {
+ mOriginalBackground = backgroundView.getBackground();
+ backgroundView.setTag(R.id.saved_floating_widget_background, mOriginalBackground);
+ } else {
+ mOriginalBackground = (Drawable) backgroundView.getTag(
+ R.id.saved_floating_widget_background);
+ }
mBackgroundProperties.init(
mOriginalBackground.getConstantState().newDrawable().mutate());
setBackground(mBackgroundProperties.mDrawable);
@@ -115,6 +129,10 @@
}
void recycle() {
+ if (mSourceView != null) {
+ mSourceView.setTag(R.id.saved_floating_widget_foreground, null);
+ mSourceView.setTag(R.id.saved_floating_widget_background, null);
+ }
mSourceView = null;
mOriginalForeground = null;
mOriginalBackground = null;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index e9498fd..5bfd035 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -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.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 @@
@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 @@
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 @@
// 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 @@
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 {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ac59403..4ba0276 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -147,8 +147,6 @@
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.DynamicResource;
@@ -186,6 +184,7 @@
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;
@@ -194,6 +193,7 @@
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;
@@ -937,7 +937,7 @@
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;
@@ -1518,9 +1518,10 @@
mMovingTaskView = null;
runningTaskView.resetPersistentViewTransforms();
int frontTaskIndex = 0;
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && !runningTaskView.isDesktopTask()) {
- // If desktop mode is enabled, desktop task view is pinned at first position.
- // Move running task to position 1
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && mDesktopTaskView != null
+ && !runningTaskView.isDesktopTask()) {
+ // If desktop mode is enabled, desktop task view is pinned at first position if present.
+ // Move running task to position 1.
frontTaskIndex = 1;
}
addView(runningTaskView, frontTaskIndex);
@@ -1653,14 +1654,18 @@
if (!taskGroups.isEmpty()) {
addView(mClearAllButton);
-
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
- && !getSplitSelectController().isSplitSelectActive()) {
- mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(TaskView.Type.DESKTOP);
- // Always add a desktop task to the first position. Even if it is empty
- addView(mDesktopTaskView, 0);
- ArrayList<Task> tasks = desktopTask != null ? desktopTask.tasks : new ArrayList<>();
- mDesktopTaskView.bind(tasks, mOrientationState);
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ // Check if we have apps on the desktop
+ if (desktopTask != null && !desktopTask.tasks.isEmpty()) {
+ // If we are actively choosing apps for split, skip the desktop tile
+ if (!getSplitSelectController().isSplitSelectActive()) {
+ mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(
+ TaskView.Type.DESKTOP);
+ // Always add a desktop task to the first position
+ addView(mDesktopTaskView, 0);
+ mDesktopTaskView.bind(desktopTask.tasks, mOrientationState);
+ }
+ }
}
}
@@ -3096,29 +3101,26 @@
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);
@@ -3164,6 +3166,8 @@
InteractionJankMonitorWrapper.cancel(
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
}
+
+ updateCurrentTaskActionsVisibility();
});
}
@@ -4447,7 +4451,7 @@
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);
@@ -4493,12 +4497,15 @@
* Attempts to initiate split with an existing taskView, if one exists
*/
public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "enterSplitSelect");
mSplitSelectSource = splitSelectSource;
mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId);
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);
@@ -4517,8 +4524,32 @@
* 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 {
@@ -4606,7 +4637,8 @@
mSecondSplitHiddenView = containerTaskView;
if (mSecondSplitHiddenView != null) {
- mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
+ mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
+ mSplitSelectStateController.getSecondTaskId());
}
InteractionJankMonitorWrapper.begin(this,
@@ -4632,10 +4664,15 @@
}
if (mSecondSplitHiddenView != null) {
- mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+ mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSecondSplitHiddenView = null;
}
+ // We are leaving split selection state, so it is safe to reset thumbnail translations for
+ // the next time split is invoked.
+ setTaskViewsPrimarySplitTranslation(0);
+ setTaskViewsSecondarySplitTranslation(0);
+
if (mSplitHiddenTaskViewIndex == -1) {
return;
}
@@ -4650,15 +4687,10 @@
}
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
- // We are leaving split selection state, so it is safe to reset thumbnail translations for
- // the next time split is invoked.
- setTaskViewsPrimarySplitTranslation(0);
- setTaskViewsSecondarySplitTranslation(0);
-
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
+ mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
@@ -5201,7 +5233,7 @@
}
private int getFirstViewIndex() {
- if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+ if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && mDesktopTaskView != null) {
// Desktop task is at position 0, that is the first view
return 0;
}
@@ -5528,7 +5560,7 @@
}
taskView.setShowScreenshot(true);
- for (TaskView.TaskIdAttributeContainer container :
+ for (TaskIdAttributeContainer container :
taskView.getTaskIdAttributeContainers()) {
if (container == null) {
continue;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 432eadc..899fea2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -81,6 +81,47 @@
}
};
+ 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 @@
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 @@
@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 @@
// 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 @@
}
}
+ /** 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 boolean shouldShowSplashView() {
return isThumbnailAspectRatioDifferentFromThumbnailData()
- || isThumbnailRotationDifferentFromTask();
+ || isThumbnailRotationDifferentFromTask()
+ || mShowSplashForSplitSelection;
+ }
+
+ public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
+ mShowSplashForSplitSelection = showSplashForSplitSelection;
}
protected void refreshSplashView() {
@@ -396,7 +471,6 @@
imageView.setScaleType(ImageView.ScaleType.MATRIX);
Matrix matrix = new Matrix();
-
float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
float viewWidth = getMeasuredWidth();
@@ -408,12 +482,13 @@
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 @@
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();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ab72f2d..df90583 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -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.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.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 @@
}
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 @@
}
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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
setAlpha(mStableAlpha);
setIconScaleAndDim(1);
setColorTint(0, 0);
+ mSnapshotView.resetViewTransforms();
}
public void setStableAlpha(float parentAlpha) {
@@ -1720,10 +1744,12 @@
}
/**
- * 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 @@
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 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;
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index bc1b87d..6c0e7dc 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -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 @@
}
@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 @@
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 @@
isRtl
)
params.setProgress(
- /* fullscreenProgress= */ 1.0f,
+ /* fullscreenProgress= */ 0f,
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f,
/* previewWidth= */ 0,
@@ -218,11 +148,12 @@
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 @@
mPreviewPositionHelper
)
- val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
- assertThat(params.mCurrentDrawnInsets).isEqualTo(expectedClippedInsets)
+ val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
+ assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
deleted file mode 100644
index f10b917..0000000
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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;
-
-import android.content.Intent;
-
-import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TaplTestsSplitscreen extends AbstractQuickStepTest {
- private static final String CALCULATOR_APP_NAME = "Calculator";
- private static final String CALCULATOR_APP_PACKAGE =
- resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
- TaplTestsLauncher3.initialize(this);
-
- mLauncher.getWorkspace()
- .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
- .switchToAllApps()
- .getAppIcon(CALCULATOR_APP_NAME)
- .dragToHotseat(0);
-
- startAppFast(CALCULATOR_APP_PACKAGE);
- mLauncher.enableBlockTimeout(true);
- mLauncher.showTaskbarIfHidden();
- }
-
- @After
- public void tearDown() {
- mLauncher.enableBlockTimeout(false);
- }
-
- @Test
- // TODO (b/270201357): When this test is proven stable, remove this TestStabilityRule and
- // introduce into presubmit as well.
- @TestStabilityRule.Stability(
- flavors = TestStabilityRule.LOCAL | TestStabilityRule.PLATFORM_POSTSUBMIT)
- @PortraitLandscape
- @TaskbarModeSwitch
- public void testSplitAppFromHomeWithItself() throws Exception {
- Assume.assumeTrue(mLauncher.isTablet());
-
- mLauncher.goHome()
- .switchToAllApps()
- .getAppIcon(CALCULATOR_APP_NAME)
- .openMenu()
- .getSplitScreenMenuItem()
- .click();
-
- mLauncher.getLaunchedAppState()
- .getTaskbar()
- .getAppIcon(CALCULATOR_APP_NAME)
- .launchIntoSplitScreen();
- }
-}
diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml
index ee710d0..2efd2b9 100644
--- a/res/drawable/ic_split_horizontal.xml
+++ b/res/drawable/ic_split_horizontal.xml
@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20dp"
- android:height="16dp"
- android:viewportWidth="20"
- android:viewportHeight="16">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:pathData="M18,14L13,14L13,2L18,2L18,14ZM20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14ZM7,14L2,14L2,2L7,2L7,14ZM9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14Z"
+ android:pathData="M4,6L9,6L9,18L4,18L4,6ZM2,6L2,18C2,19.1 2.9,20 4,20L9,20C10.1,20 11,19.1 11,18L11,6C11,4.9 10.1,4 9,4L4,4C2.9,4 2,4.9 2,6ZM15,6L20,6L20,18L15,18L15,6ZM13,6L13,18C13,19.1 13.9,20 15,20L20,20C21.1,20 22,19.1 22,18L22,6C22,4.9 21.1,4 20,4L15,4C13.9,4 13,4.9 13,6Z"
android:fillColor="#000000"/>
</vector>
diff --git a/res/layout/all_apps_icon_twoline.xml b/res/layout/all_apps_icon_twoline.xml
index 54c7147..b0d02c1 100644
--- a/res/layout/all_apps_icon_twoline.xml
+++ b/res/layout/all_apps_icon_twoline.xml
@@ -19,7 +19,6 @@
android:id="@+id/icon"
android:singleLine="false"
android:lines="2"
- android:inputType="textMultiLine"
launcher:iconDisplay="all_apps"
launcher:centerVertically="true" />
diff --git a/res/layout/system_shortcut_icons_container.xml b/res/layout/system_shortcut_icons_container.xml
index ee104d9..dc4fdb3 100644
--- a/res/layout/system_shortcut_icons_container.xml
+++ b/res/layout/system_shortcut_icons_container.xml
@@ -22,11 +22,4 @@
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:background="@drawable/single_item_primary"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:clipToPadding="true">
-
- <Space android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:id="@+id/separator"/>
-</LinearLayout>
+ android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_icons_container_material_u.xml b/res/layout/system_shortcut_icons_container_material_u.xml
index afd11e6..70950ba 100644
--- a/res/layout/system_shortcut_icons_container_material_u.xml
+++ b/res/layout/system_shortcut_icons_container_material_u.xml
@@ -23,10 +23,4 @@
android:orientation="horizontal"
android:gravity="end|center_vertical"
android:background="@drawable/popup_background_material_u"
- android:elevation="@dimen/deep_shortcuts_elevation">
-
- <Space android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:id="@+id/separator"/>
-</LinearLayout>
+ android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_rows_container_material_u.xml b/res/layout/system_shortcut_rows_container.xml
similarity index 100%
rename from res/layout/system_shortcut_rows_container_material_u.xml
rename to res/layout/system_shortcut_rows_container.xml
diff --git a/res/layout/system_shortcut_spacer.xml b/res/layout/system_shortcut_spacer.xml
new file mode 100644
index 0000000..60ea9ec
--- /dev/null
+++ b/res/layout/system_shortcut_spacer.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Space
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:id="@+id/shortcut_spacer"/>
\ No newline at end of file
diff --git a/res/values/id.xml b/res/values/id.xml
index 375750f..dc81944 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -38,4 +38,7 @@
<item type="id" name="cache_entry_tag_id" />
<item type="id" name="saved_clip_children_tag_id" />
+
+ <item type="id" name="saved_floating_widget_foreground" />
+ <item type="id" name="saved_floating_widget_background" />
</resources>
diff --git a/res/xml/default_tapl_test_workspace.xml b/res/xml/default_tapl_test_workspace.xml
new file mode 100644
index 0000000..24d76f3
--- /dev/null
+++ b/res/xml/default_tapl_test_workspace.xml
@@ -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>
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index eb6d096..9d5b08e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,6 +2,7 @@
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 @@
*/
@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 @@
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 @@
try {
if (!cursor.moveToFirst()) {
// The widget no long exists.
- holder.deleteAppWidgetId(newWidgetIds[i]);
+ host.deleteAppWidgetId(newWidgetIds[i]);
}
} finally {
cursor.close();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3eb03ed..865edcd 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -52,8 +52,10 @@
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.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 @@
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 @@
private FastBitmapDrawable mIcon;
private boolean mCenterVertically;
- protected final int mDisplay;
+ protected int mDisplay;
private final CheckLongPressHelper mLongPressHelper;
@@ -255,6 +267,8 @@
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
+ setSingleLine(true);
+ setMaxLines(1);
setTag(null);
if (mIconLoadRequest != null) {
@@ -382,8 +396,15 @@
}
@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 @@
}
}
+ /** 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,28 @@
setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
getPaddingBottom());
}
+ // only apply two line for all_apps
+ if (((FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && mDisplay == DISPLAY_ALL_APPS)
+ || (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
+ && mDisplay == DISPLAY_SEARCH_RESULT)) && (mLastOriginalText != null)) {
+ 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 +746,73 @@
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 +833,7 @@
|| 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);
}
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 87ee4f6..d992ee0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -193,7 +193,7 @@
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 @@
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 @@
*/
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 @@
}
// 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 @@
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;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 767c3d9..e688709 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -107,6 +107,7 @@
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 @@
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 @@
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 @@
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,
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 76cae6a..cef00d9 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -41,10 +41,6 @@
* An animation using the view's background.
*/
public static final int VIEW_BACKGROUND = 1;
- /**
- * The default animation for a given view/item info type, but without the splash icon.
- */
- public static final int DEFAULT_NO_ICON = 2;
}
/**
@@ -393,6 +389,7 @@
"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";
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c7431ed..b5bc60d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,8 +16,6 @@
package com.android.launcher3;
-import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-
import static com.android.launcher3.anim.Interpolators.SCROLL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
@@ -52,7 +50,6 @@
import android.widget.ScrollView;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -319,7 +316,6 @@
/**
* Returns an IntSet with the indices of the currently visible pages
*/
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public IntSet getVisiblePageIndices() {
return getPageIndices(mCurrentPage);
}
@@ -1370,8 +1366,14 @@
int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
mActivePointerId);
float delta = primaryDirection - mDownMotionPrimary;
- int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(
- getPageAt(mCurrentPage))
+
+ View current = getPageAt(mCurrentPage);
+ if (current == null) {
+ Log.e(TAG, "current page was null. this should not happen.");
+ return true;
+ }
+
+ int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(current)
* mOrientationHandler.getPrimaryScale(this));
boolean isSignificantMove = isSignificantMove(Math.abs(delta), pageOrientedSize);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 54bf6a8..85d7a05 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -355,7 +355,7 @@
});
}
- if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
+ if(FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.userControlled
&& Utilities.ATLEAST_S) {
if (toState == ALL_APPS) {
builder.addOnFrameListener(
@@ -385,8 +385,9 @@
builder.add(anim);
setAlphas(toState, config, builder);
-
- if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
+ // This controls both haptics for tapping on QSB and going to all apps.
+ if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) &&
+ !FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get()) {
mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 7040de5..8fa4276 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -33,6 +33,7 @@
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 @@
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 @@
mOnIconClickListener = mActivityContext.getItemOnClickListener();
mAdapterProvider = adapterProvider;
- mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
+ mExtraTextHeight = Utilities.calculateTextHeight(
+ mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
}
/**
@@ -197,7 +199,7 @@
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:
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index b73621d..2380af4 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -55,6 +55,11 @@
mUpdateCallback = updateCallback;
}
+ public AnimatedFloat(Runnable updateCallback, float initialValue) {
+ this(updateCallback);
+ value = initialValue;
+ }
+
/**
* Returns an animation from the current value to the given value.
*/
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 98b61d1..b7e6378 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -113,6 +113,10 @@
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");
@@ -352,8 +356,9 @@
"Enable the ability to tap a staged app during split select to launch it in full screen"
);
- public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(270396358,
- "ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
+ public static final BooleanFlag ENABLE_PREMIUM_HAPTICS_ALL_APPS = getDebugFlag(270396358,
+ "ENABLE_PREMIUM_HAPTICS_ALL_APPS", false,
+ "Enables haptics opening/closing All apps");
public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(270396209,
"ENABLE_FORCED_MONO_ICON", false,
@@ -373,6 +378,11 @@
"Enables taskbar pinning to allow user to switch between transient and persistent "
+ "taskbar flavors");
+ public static final BooleanFlag ENABLE_WORKSPACE_LOADING_OPTIMIZATION = getDebugFlag(251502424,
+ "ENABLE_WORKSPACE_LOADING_OPTIMIZATION", false, "load the current workspace screen "
+ + "visible to the user before the rest rather than loading all of them at once."
+ );
+
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
"ENABLE_GRID_ONLY_OVERVIEW", false,
"Enable a grid-only overview without a focused task.");
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index e983a30..be995bc 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -126,8 +126,14 @@
mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
view.getResources().getDisplayMetrics());
mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
- mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
- mHideSysUiScrim = mTopScrim == null;
+ if (mTopScrim != null) {
+ mTopScrim.setDither(true);
+ mBottomMask = createDitheredAlphaMask();
+ mHideSysUiScrim = false;
+ } else {
+ mBottomMask = null;
+ mHideSysUiScrim = true;
+ }
mDrawWallpaperScrim = FeatureFlags.ENABLE_WALLPAPER_SCRIM.get()
&& !Themes.getAttrBoolean(view.getContext(), R.attr.isMainColorDark)
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5f6df27..cf710da 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -636,6 +636,12 @@
@UiEvent(doc = "User scrolled up on the search result page.")
LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP(1286),
+
+ @UiEvent(doc = "User or automatic timeout has hidden transient taskbar.")
+ LAUNCHER_TRANSIENT_TASKBAR_HIDE(1330),
+
+ @UiEvent(doc = "User has swiped upwards from the gesture handle to show transient taskbar.")
+ LAUNCHER_TRANSIENT_TASKBAR_SHOW(1331),
;
// ADD MORE
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 8519a3e..91ace27 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -27,6 +27,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -42,8 +43,10 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -77,6 +80,36 @@
* Binds all loaded data to actual views on the main thread.
*/
public void bindWorkspace(boolean incrementBindId) {
+ if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
+ DisjointWorkspaceBinder workspaceBinder =
+ initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
+ workspaceBinder.bindCurrentWorkspacePages();
+ workspaceBinder.bindOtherWorkspacePages();
+ } else {
+ bindWorkspaceAllAtOnce(incrementBindId);
+ }
+ }
+
+ /**
+ * Initializes the WorkspaceBinder for binding.
+ *
+ * @param incrementBindId this is used to stop previously started binding tasks that are
+ * obsolete but still queued.
+ * @param workspacePages this allows the Launcher to add the correct workspace screens.
+ */
+ public DisjointWorkspaceBinder initWorkspaceBinder(boolean incrementBindId,
+ IntArray workspacePages) {
+
+ synchronized (mBgDataModel) {
+ if (incrementBindId) {
+ mBgDataModel.lastBindId++;
+ }
+ mMyBindingId = mBgDataModel.lastBindId;
+ return new DisjointWorkspaceBinder(workspacePages);
+ }
+ }
+
+ private void bindWorkspaceAllAtOnce(boolean incrementBindId) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
@@ -95,7 +128,7 @@
}
for (Callbacks cb : mCallbacksList) {
- new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
+ new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
}
}
@@ -180,7 +213,7 @@
return idleLock;
}
- private class WorkspaceBinder {
+ private class UnifiedWorkspaceBinder {
private final Executor mUiExecutor;
private final Callbacks mCallbacks;
@@ -194,7 +227,7 @@
private final IntArray mOrderedScreenIds;
private final ArrayList<FixedContainerItems> mExtraItems;
- WorkspaceBinder(Callbacks callbacks,
+ UnifiedWorkspaceBinder(Callbacks callbacks,
Executor uiExecutor,
LauncherAppState app,
BgDataModel bgDataModel,
@@ -320,4 +353,115 @@
});
}
}
+
+ private class DisjointWorkspaceBinder {
+ private final IntArray mOrderedScreenIds;
+ private final IntSet mCurrentScreenIds = new IntSet();
+ private final Set<Integer> mBoundItemIds = new HashSet<>();
+
+ protected DisjointWorkspaceBinder(IntArray orderedScreenIds) {
+ mOrderedScreenIds = orderedScreenIds;
+
+ for (Callbacks cb : mCallbacksList) {
+ mCurrentScreenIds.addAll(cb.getPagesToBindSynchronously(orderedScreenIds));
+ }
+ if (mCurrentScreenIds.size() == 0) {
+ mCurrentScreenIds.add(Workspace.FIRST_SCREEN_ID);
+ }
+ }
+
+ /**
+ * Binds the currently loaded items in the Data Model. Also signals to the Callbacks[]
+ * that these items have been bound and their respective screens are ready to be shown.
+ *
+ * If this method is called after all the items on the workspace screen have already been
+ * loaded, it will bind all workspace items immediately, and bindOtherWorkspacePages() will
+ * not bind any items.
+ */
+ protected void bindCurrentWorkspacePages() {
+ // Save a copy of all the bg-thread collections
+ ArrayList<ItemInfo> workspaceItems;
+ ArrayList<LauncherAppWidgetInfo> appWidgets;
+
+ synchronized (mBgDataModel) {
+ workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
+ appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
+ }
+
+ workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
+ appWidgets.forEach(it -> mBoundItemIds.add(it.id));
+
+ sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
+
+ // Tell the workspace that we're about to start binding items
+ executeCallbacksTask(c -> {
+ c.clearPendingBinds();
+ c.startBinding();
+ }, mUiExecutor);
+
+ // Bind workspace screens
+ executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
+
+ bindWorkspaceItems(workspaceItems);
+ bindAppWidgets(appWidgets);
+
+ executeCallbacksTask(c -> {
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ c.onInitialBindComplete(mCurrentScreenIds, new RunnableList());
+ }, mUiExecutor);
+ }
+
+ protected void bindOtherWorkspacePages() {
+ // Save a copy of all the bg-thread collections
+ ArrayList<ItemInfo> workspaceItems;
+ ArrayList<LauncherAppWidgetInfo> appWidgets;
+
+ synchronized (mBgDataModel) {
+ workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
+ appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
+ }
+
+ workspaceItems.removeIf(it -> mBoundItemIds.contains(it.id));
+ appWidgets.removeIf(it -> mBoundItemIds.contains(it.id));
+
+ sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
+
+ bindWorkspaceItems(workspaceItems);
+ bindAppWidgets(appWidgets);
+
+ executeCallbacksTask(c -> c.finishBindingItems(mCurrentScreenIds), mUiExecutor);
+ mUiExecutor.execute(() -> {
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ ItemInstallQueue.INSTANCE.get(mApp.getContext())
+ .resumeModelPush(FLAG_LOADER_RUNNING);
+ });
+
+ for (Callbacks cb : mCallbacksList) {
+ cb.bindStringCache(mBgDataModel.stringCache.clone());
+ }
+ }
+
+ private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems) {
+ // Bind the workspace items
+ int count = workspaceItems.size();
+ for (int i = 0; i < count; i += ITEMS_CHUNK) {
+ final int start = i;
+ final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
+ executeCallbacksTask(
+ c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
+ mUiExecutor);
+ }
+ }
+
+ private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets) {
+ // Bind the widgets, one at a time
+ int count = appWidgets.size();
+ for (int i = 0; i < count; i++) {
+ final ItemInfo widget = appWidgets.get(i);
+ executeCallbacksTask(
+ c -> c.bindItems(Collections.singletonList(widget), false),
+ mUiExecutor);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index a0f21dc..be3a09b 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -60,9 +60,7 @@
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
/**
* A container for shortcuts to deep links and notifications associated with an app.
@@ -337,23 +335,6 @@
}
/**
- * Shows the popup at the desired location, optionally reversing the children.
- * @param viewsToFlip number of views from the top to to flip in case of reverse order
- */
- protected void reorderAndShow(int viewsToFlip) {
- setupForDisplay();
- boolean reverseOrder = !ENABLE_MATERIAL_U_POPUP.get() && mIsAboveIcon;
- if (reverseOrder) {
- reverseOrder(viewsToFlip);
- }
- assignMarginsAndBackgrounds(this);
- if (shouldAddArrow()) {
- addArrow();
- }
- animateOpen();
- }
-
- /**
* Shows the popup at the desired location.
*/
public void show() {
@@ -372,22 +353,6 @@
orientAboutObject();
}
- private void reverseOrder(int viewsToFlip) {
- int count = getChildCount();
- ArrayList<View> allViews = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- if (i == viewsToFlip) {
- Collections.reverse(allViews);
- }
- allViews.add(getChildAt(i));
- }
- Collections.reverse(allViews);
- removeAllViews();
- for (int i = 0; i < count; i++) {
- addView(allViews.get(i));
- }
- }
-
private int getArrowLeft() {
if (mIsLeftAligned) {
return mArrowOffsetHorizontal;
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8fef5c6..43ca2a6 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -227,17 +227,18 @@
if (ENABLE_MATERIAL_U_POPUP.get()) {
container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container_material_u, launcher.getDragLayer(), false);
+ container.configureForLauncher(launcher);
container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
} else {
container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
+ container.configureForLauncher(launcher);
container.populateAndShow(
icon,
deepShortcutCount,
popupDataProvider.getNotificationKeysForItem(item),
systemShortcuts);
}
- container.configureForLauncher(launcher);
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
container.requestFocus();
return container;
@@ -257,14 +258,19 @@
}
// If there is only 1 shortcut, add it to its own container so it can show text and icon
if (shortcuts.size() == 1) {
- initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
+ mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_rows_container,
+ this, 0);
+ initializeSystemShortcut(R.layout.system_shortcut, mSystemShortcutContainer,
+ shortcuts.get(0), false);
return;
}
- mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this);
- for (SystemShortcut shortcut : shortcuts) {
+ mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this, 0);
+ for (int i = 0; i < shortcuts.size(); i++) {
initializeSystemShortcut(
- R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
- shortcut);
+ R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer,
+ shortcuts.get(i),
+ i < shortcuts.size() - 1);
}
}
@@ -289,7 +295,6 @@
}
updateNotificationHeader();
}
- int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
if (mDeepShortcutContainer == null) {
mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
@@ -314,8 +319,7 @@
Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(shortcuts);
if (widgetShortcutOpt.isPresent()) {
if (mWidgetContainer == null) {
- mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
- this);
+ mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container, this, 0);
}
initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
}
@@ -324,14 +328,17 @@
} else {
mDeepShortcutContainer.setVisibility(View.GONE);
if (!shortcuts.isEmpty()) {
- for (SystemShortcut shortcut : shortcuts) {
- initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
+ for (int i = 0; i < shortcuts.size(); i++) {
+ initializeSystemShortcut(
+ R.layout.system_shortcut,
+ this,
+ shortcuts.get(i),
+ i < shortcuts.size() - 1);
}
}
}
-
- reorderAndShow(viewsToFlip);
- showPopupContainer((ItemInfo) originalIcon.getTag(), notificationKeys);
+ show();
+ loadAppShortcuts((ItemInfo) originalIcon.getTag(), notificationKeys);
}
/**
@@ -351,19 +358,17 @@
addAllShortcutsMaterialU(deepShortcutCount, systemShortcuts);
} else if (!systemShortcuts.isEmpty()) {
addSystemShortcutsMaterialU(systemShortcuts,
- R.layout.system_shortcut_rows_container_material_u,
+ R.layout.system_shortcut_rows_container,
R.layout.system_shortcut);
}
-
- // no reversing needed for U design
- reorderAndShow(0);
- showPopupContainer((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
+ show();
+ loadAppShortcuts((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
}
/**
* Animates and loads shortcuts on background thread for this popup container
*/
- private void showPopupContainer(ItemInfo originalItemInfo,
+ private void loadAppShortcuts(ItemInfo originalItemInfo,
List<NotificationKeyData> notificationKeys) {
if (ATLEAST_P) {
@@ -390,7 +395,7 @@
if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) {
// add all system shortcuts including widgets shortcut to same container
addSystemShortcutsMaterialU(systemShortcuts,
- R.layout.system_shortcut_rows_container_material_u,
+ R.layout.system_shortcut_rows_container,
R.layout.system_shortcut);
addDeepShortcutsMaterialU(deepShortcutCount);
return;
@@ -458,8 +463,13 @@
return;
}
mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this);
- for (SystemShortcut shortcut : systemShortcuts) {
- initializeSystemShortcut(systemShortcutLayout, mSystemShortcutContainer, shortcut);
+ mWidgetContainer = mSystemShortcutContainer;
+ for (int i = 0; i < systemShortcuts.size(); i++) {
+ initializeSystemShortcut(
+ systemShortcutLayout,
+ mSystemShortcutContainer,
+ systemShortcuts.get(i),
+ i < systemShortcuts.size() - 1);
}
}
@@ -533,20 +543,31 @@
}
protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) {
- View view = initializeSystemShortcut(R.layout.system_shortcut, container, info);
+ View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false);
view.getLayoutParams().width = mContainerWidth;
}
- protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
- View view = inflateAndAdd(
- resId, container, getInsertIndexForSystemShortcut(container, info));
+ /**
+ * Initializes and adds View for given SystemShortcut to a container.
+ * @param resId Resource id to use for SystemShortcut View.
+ * @param container ViewGroup to add the shortcut View to as a parent
+ * @param info The SystemShortcut instance to create a View for.
+ * @param shouldAddSpacer If True, will add a spacer after the shortcut, when showing the
+ * SystemShortcut as an icon only. Used to space the shortcut icons
+ * evenly.
+ * @return The view inflated for the SystemShortcut
+ */
+ protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info,
+ boolean shouldAddSpacer) {
+ View view = inflateAndAdd(resId, container);
if (view instanceof DeepShortcutView) {
- // Expanded system shortcut, with both icon and text shown on white background.
+ // System shortcut takes entire row with icon and text
final DeepShortcutView shortcutView = (DeepShortcutView) view;
info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
} else if (view instanceof ImageView) {
- // Only the system shortcut icon shows on a gray background header.
+ // System shortcut is just an icon
info.setIconAndContentDescriptionFor((ImageView) view);
+ if (shouldAddSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container);
view.setTooltipText(view.getContentDescription());
}
view.setTag(info);
@@ -555,17 +576,6 @@
}
/**
- * Returns an index for inserting a shortcut into a container.
- */
- private int getInsertIndexForSystemShortcut(ViewGroup container, SystemShortcut shortcut) {
- final View separator = container.findViewById(R.id.separator);
-
- return separator != null && shortcut.isLeftGroup() ?
- container.indexOfChild(separator) :
- container.getChildCount();
- }
-
- /**
* Determines when the deferred drag should be started.
*
* Current behavior:
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 2a452be..3a5ef10 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -21,8 +21,10 @@
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
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.database.Cursor;
@@ -49,7 +51,6 @@
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
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;
@@ -354,12 +355,11 @@
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.");
}
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index c66f3a1..28fc4f0 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -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}.
@@ -59,6 +66,41 @@
}
/**
+ * 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}.
*/
public static class StringMatcher {
@@ -118,7 +160,11 @@
}
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
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 628aa9a..050e88f 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -582,8 +582,7 @@
? 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 @@
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 @@
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 @@
: 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()) {
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 0b5bc8d..1cb7a45 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -33,7 +33,7 @@
public static final int INDEX_REORDER_PREVIEW_OFFSET = 1;
public static final int INDEX_MOVE_FROM_CENTER_ANIM = 2;
- // Specific for icons and folders
+ // Specific for items in taskbar (icons, folders, qsb)
public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index e2f1c04..cb6a46c 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -76,6 +76,7 @@
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+ private static final float VIEW_NO_SCALE = 1f;
protected final T mActivityContext;
@@ -93,7 +94,8 @@
protected @Nullable OnCloseListener mOnCloseBeginListener;
protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
- private final AnimatedFloat mSlidInViewScale = new AnimatedFloat(this::onScaleProgressChanged);
+ private final AnimatedFloat mSlideInViewScale =
+ new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
private boolean mIsBackProgressing;
@Nullable private Drawable mContentBackground;
@@ -184,12 +186,12 @@
float deceleratedProgress =
Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
mIsBackProgressing = progress > 0f;
- mSlidInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
+ mSlideInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
+ (1 - PREDICTIVE_BACK_MIN_SCALE) * (1 - deceleratedProgress));
}
private void onScaleProgressChanged() {
- float scaleProgress = mSlidInViewScale.value;
+ float scaleProgress = mSlideInViewScale.value;
SCALE_PROPERTY.set(this, scaleProgress);
setClipChildren(!mIsBackProgressing);
mContent.setClipChildren(!mIsBackProgressing);
@@ -209,7 +211,7 @@
}
protected void animateSlideInViewToNoScale() {
- mSlidInViewScale.animateToValue(1f)
+ mSlideInViewScale.animateToValue(1f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
.start();
}
@@ -236,8 +238,8 @@
/** 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);
}
/**
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 10f40b7..b6f6223 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -45,7 +45,6 @@
import android.view.WindowInsetsController;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
-import android.window.SplashScreen;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -321,14 +320,7 @@
return false;
}
- Bundle optsBundle = null;
- if (v != null) {
- optsBundle = getActivityLaunchOptions(v, item).toBundle();
- } else if (item != null && item.animationType == LauncherSettings.Animation.DEFAULT_NO_ICON
- && Utilities.ATLEAST_T) {
- optsBundle = ActivityOptions.makeBasic()
- .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR).toBundle();
- }
+ Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null;
UserHandle user = item == null ? null : item.user;
// Prepare intent
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 55af622..e233e46 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -95,6 +95,9 @@
private ClipIconView mClipIconView;
private @Nullable Drawable mBadge;
+ // A view whose visibility should update in sync with mOriginalIcon.
+ private @Nullable View mMatchVisibilityView;
+
private View mOriginalIcon;
private RectF mPositionOut;
private Runnable mOnTargetChangeRunnable;
@@ -386,7 +389,7 @@
* Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
* callback to set the icon once the icon result is loaded.
*/
- private void checkIconResult(View originalView) {
+ private void checkIconResult() {
CancellationSignal cancellationSignal = new CancellationSignal();
if (mIconLoadResult == null) {
@@ -399,7 +402,7 @@
setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
setVisibility(VISIBLE);
- setIconAndDotVisible(originalView, false);
+ updateViewsVisibility(false /* isVisible */);
} else {
mIconLoadResult.onIconLoaded = () -> {
if (cancellationSignal.isCanceled()) {
@@ -410,7 +413,7 @@
mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
setVisibility(VISIBLE);
- setIconAndDotVisible(originalView, false);
+ updateViewsVisibility(false /* isVisible */);
};
mLoadIconSignal = cancellationSignal;
}
@@ -481,9 +484,9 @@
// No need to wait for icon load since we can display the BubbleTextView drawable.
setVisibility(View.VISIBLE);
}
- if (!mIsOpening && mOriginalIcon != null) {
+ if (!mIsOpening) {
// When closing an app, we want the item on the workspace to be invisible immediately
- setIconAndDotVisible(mOriginalIcon, false);
+ updateViewsVisibility(false /* isVisible */);
}
}
@@ -562,13 +565,14 @@
/**
* Creates a floating icon view for {@param originalView}.
* @param originalView The view to copy
+ * @param secondView A view whose visibility should update in sync with originalView.
* @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
* Else, we will not draw anything in this view.
* @param positionOut Rect that will hold the size and position of v.
* @param isOpening True if this view replaces the icon for app open animation.
*/
public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
- boolean hideOriginal, RectF positionOut, boolean isOpening) {
+ @Nullable View secondView, boolean hideOriginal, RectF positionOut, boolean isOpening) {
final DragLayer dragLayer = launcher.getDragLayer();
ViewGroup parent = (ViewGroup) dragLayer.getParent();
FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view,
@@ -578,6 +582,7 @@
// Init properties before getting the drawable.
view.mIsOpening = isOpening;
view.mOriginalIcon = originalView;
+ view.mMatchVisibilityView = secondView;
view.mPositionOut = positionOut;
// Get the drawable on the background thread
@@ -597,7 +602,8 @@
view.matchPositionOf(launcher, originalView, isOpening, positionOut);
// We need to add it to the overlay, but keep it invisible until animation starts..
- setIconAndDotVisible(view, false);
+ view.setVisibility(View.INVISIBLE);
+
parent.addView(view);
dragLayer.addView(view.mListenerView);
view.mListenerView.setListener(view::fastFinish);
@@ -606,7 +612,7 @@
view.mEndRunnable = null;
if (hideOriginal) {
- setIconAndDotVisible(originalView, true);
+ view.updateViewsVisibility(true /* isVisible */);
view.finish(dragLayer);
} else {
view.finish(dragLayer);
@@ -617,12 +623,21 @@
// Must be called after the fastFinish listener and end runnable is created so that
// the icon is not left in a hidden state.
if (shouldLoadIcon) {
- view.checkIconResult(originalView);
+ view.checkIconResult();
}
return view;
}
+ private void updateViewsVisibility(boolean isVisible) {
+ if (mOriginalIcon != null) {
+ setIconAndDotVisible(mOriginalIcon, isVisible);
+ }
+ if (mMatchVisibilityView != null) {
+ setIconAndDotVisible(mMatchVisibilityView, isVisible);
+ }
+ }
+
private void finish(DragLayer dragLayer) {
((ViewGroup) dragLayer.getParent()).removeView(this);
dragLayer.removeView(mListenerView);
diff --git a/src/com/android/launcher3/views/IconButtonView.java b/src/com/android/launcher3/views/IconButtonView.java
index 71f6756..0ac1919 100644
--- a/src/com/android/launcher3/views/IconButtonView.java
+++ b/src/com/android/launcher3/views/IconButtonView.java
@@ -29,6 +29,7 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
@@ -78,6 +79,12 @@
}
}
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ event.getText().add(this.getContentDescription());
+ }
+
/** Sets given Drawable as icon */
public void setIconDrawable(@NonNull Drawable drawable) {
ColorStateList tintList = getBackgroundTintList();
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index bea7517..8e67eb1 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -322,13 +322,6 @@
}
/**
- * Delete the host
- */
- public void deleteHost() {
- mWidgetHost.deleteHost();
- }
-
- /**
* @return The app widget ids
*/
@NonNull
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index dd035fc..c7db2ae 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -107,6 +107,7 @@
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";
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
index 3b53255..0ba46f4 100644
--- a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -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 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 @@
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));
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
new file mode 100644
index 0000000..d07c6c2
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testEmptyString_flagOff() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testStringWithSpaceLongerThanCharLimit_flagOn() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testStringWithSpaceLongerThanCharLimit_flagOff() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringSymbolLongerThanCharLimit_flagOn() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongStringSymbolLongerThanCharLimit_flagOff() {
+ try (AutoCloseable flag = 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());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @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);
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5f516eb..f910a92 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -225,6 +225,10 @@
@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.
@@ -506,7 +510,6 @@
@Test
@PortraitLandscape
- @ScreenRecord // (b/256659409)
public void testUninstallFromAllApps() throws Exception {
installDummyAppAndWaitForUIUpdate();
try {
@@ -515,6 +518,8 @@
allApps.freeze();
try {
workspace = allApps.getAppIcon(DUMMY_APP_NAME).uninstall();
+ // After the toast clears, then the model tries to commit the uninstall transaction
+ mLauncher.waitForModelQueueCleared();
} finally {
allApps.unfreeze();
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 9669010..bf9eb5a 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -42,6 +42,7 @@
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.testcomponent.RequestPinItemActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
@@ -75,6 +76,7 @@
super.setUp();
mCallbackAction = UUID.randomUUID().toString();
mShortcutId = UUID.randomUUID().toString();
+ TaplTestsLauncher3.initialize(this);
}
@Test
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 667290f..82d9630 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -54,14 +54,5 @@
return createMenuItem(menuItem);
}
- /**
- * Returns a menu item that matches the text "Split screen". Fails if it doesn't exist.
- */
- public SplitScreenMenuItem getSplitScreenMenuItem() {
- final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
- AppIcon.getAppIconSelector("Split screen", mLauncher));
- return new SplitScreenMenuItem(mLauncher, menuItem);
- }
-
protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 48e327f..3dcb437 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -76,27 +76,6 @@
}
}
- /**
- * Clicks a launcher object to initiate splitscreen, where the selected app will be one of two
- * apps running on the screen. Should be called when Launcher is in a "split staging" state
- * and is waiting for the user's selection of a second app. Expects a SPLIT_START_EVENT to be
- * fired when the click is executed.
- */
- public LaunchedAppState launchIntoSplitScreen() {
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to launch split tasks from " + launchableType())) {
- LauncherInstrumentation.log("Launchable.launch before click "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
- mLauncher.clickLauncherObject(mObject);
-
- try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
- return new LaunchedAppState(mLauncher);
- }
- }
- }
-
protected LaunchedAppState assertAppLaunched(BySelector selector) {
mLauncher.assertTrue(
"App didn't start: (" + selector + ")",
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 52994a5..5c4b707 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1860,6 +1860,15 @@
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);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 90f3d13..adc993d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -37,9 +37,10 @@
public final class OverviewTask {
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
- static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync");
- static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect");
- static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks");
+ static final Pattern TASK_START_EVENT =
+ Pattern.compile("startActivityFromRecentsAsync");
+ static final Pattern SPLIT_START_EVENT =
+ Pattern.compile("launchSplitTasks");
private final LauncherInstrumentation mLauncher;
private final UiObject2 mTask;
private final BaseOverview mOverview;
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java b/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
deleted file mode 100644
index 47cf20b..0000000
--- a/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.tapl;
-
-import androidx.test.uiautomator.UiObject2;
-
-import com.android.launcher3.testing.shared.TestProtocol;
-
-/**
- * A class representing the "Split screen" menu item in the app long-press menu. Used for TAPL
- * testing in a similar way as other menu items {@link AppIconMenuItem}, but unlike AppIconMenuItem,
- * the split screen command does not trigger an app launch. Instead, it causes Launcher to shift to
- * a different state (OverviewSplitSelect).
- */
-public final class SplitScreenMenuItem {
- private final LauncherInstrumentation mLauncher;
- private final UiObject2 mObject;
-
- SplitScreenMenuItem(LauncherInstrumentation launcher, UiObject2 object) {
- mLauncher = launcher;
- mObject = object;
- }
-
- /**
- * Executes a click command on this menu item. Expects a SPLIT_SELECT_EVENT to be fired.
- */
- public void click() {
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to enter split select from app long-press menu")) {
- LauncherInstrumentation.log("clicking on split screen menu item "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
- mLauncher.clickLauncherObject(mObject);
-
- try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_SELECT_EVENT);
- mLauncher.waitForLauncherObject("split_placeholder");
- }
- }
- }
-}