Merge "Fix bug where hotseat disappears" into main
diff --git a/go/quickstep/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
index df09124..b1a6202 100644
--- a/go/quickstep/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -121,6 +121,17 @@
android:layout_weight="1"
android:visibility="gone" />
+ </LinearLayout>
+
+ <!-- Unused. Included only for compatibility with parent class. -->
+ <LinearLayout
+ android:id="@+id/group_action_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="top|center_horizontal"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
<Button
android:id="@+id/action_save_app_pair"
style="@style/GoOverviewActionButton"
@@ -128,8 +139,8 @@
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_save_app_pair_up_down"
android:text="@string/action_save_app_pair"
- android:theme="@style/ThemeControlHighlightWorkspaceColor"
- android:visibility="gone" />
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
</LinearLayout>
</com.android.quickstep.views.GoOverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
index 98aab67..d722dd7 100644
--- a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
+++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
@@ -13,12 +13,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
- <solid android:color="@color/bubblebar_drop_target_bg_color" />
- <stroke
- android:width="1dp"
- android:color="?androidprv:attr/materialColorPrimaryContainer" />
-</shape>
+ android:inset="@dimen/bubble_expanded_view_drop_target_padding">
+ <shape
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
+ <solid android:color="@color/bubblebar_drop_target_bg_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
+ </shape>
+</inset>
diff --git a/quickstep/res/layout/bubble_expanded_view_drop_target.xml b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
index 15ec49a..3bd5d31 100644
--- a/quickstep/res/layout/bubble_expanded_view_drop_target.xml
+++ b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
@@ -16,8 +16,8 @@
<!-- TODO(b/330585402): replace 600dp height with calculated value -->
<View xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/bubble_expanded_view_drop_target_width"
- android:layout_height="600dp"
+ android:layout_width="@dimen/bubble_expanded_view_drop_target_default_width"
+ android:layout_height="@dimen/bubble_expanded_view_drop_target_default_height"
android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
android:background="@drawable/bg_bubble_expanded_view_drop_target"
android:elevation="@dimen/bubblebar_elevation" />
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index d086da4..7aaf744 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -45,14 +45,24 @@
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/group_action_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/overview_actions_height"
+ android:layout_gravity="bottom|center_horizontal"
+ android:orientation="horizontal"
+ android:visibility="gone">
+
<Button
android:id="@+id/action_save_app_pair"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_save_app_pair"
- android:theme="@style/ThemeControlHighlightWorkspaceColor"
- android:visibility="gone" />
+ android:theme="@style/ThemeControlHighlightWorkspaceColor" />
+
</LinearLayout>
</com.android.quickstep.views.OverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 9f648a7..cc3b30e 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,10 +28,7 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <com.android.quickstep.views.TaskThumbnailViewDeprecated
- android:id="@+id/snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 36d7f86..89e9b3d 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -42,10 +42,7 @@
views that do not inherint from TaskView only or create a generic TaskView that have
N number of tasks.
-->
- <com.android.quickstep.views.TaskThumbnailViewDeprecated
- android:id="@+id/snapshot"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ <include layout="@layout/task_thumbnail"
android:visibility="gone" />
<ViewStub
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index ec657bd..87a0f70 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,15 +33,10 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <com.android.quickstep.views.TaskThumbnailViewDeprecated
- android:id="@+id/snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail"/>
- <com.android.quickstep.views.TaskThumbnailViewDeprecated
- android:id="@+id/bottomright_snapshot"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include layout="@layout/task_thumbnail"
+ android:id="@+id/bottomright_snapshot" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
new file mode 100644
index 0000000..f1a3d62
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2024 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.
+-->
+<com.android.quickstep.views.TaskThumbnailViewDeprecated
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 5097ff9..af35fc3 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen der Systemsteuerung"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Teilen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
- <string name="action_split" msgid="2098009717623550676">"Teilen"</string>
+ <string name="action_split" msgid="2098009717623550676">"Splitscreen"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"App-Paar speichern"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Für Splitscreen auf weitere App tippen"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Für Splitscreen andere App auswählen"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 159226e..0d43f1e 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -118,7 +118,7 @@
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar siempre la barra de tareas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para mostrar siempre la barra de tareas en la parte inferior, mantén pulsada la línea divisoria"</string>
<string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén pulsada la tecla de acción para buscar lo que ves en pantalla"</string>
- <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"El producto usa la parte seleccionada de tu pantalla para hacer búsquedas. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y los <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Términos del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de tu pantalla para hacer búsquedas. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y los <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Términos del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Hecho"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Inicio"</string>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 766dabb..c445bc8 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Süsteemi navigeerimisseaded"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaga"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Ekraanipilt"</string>
- <string name="action_split" msgid="2098009717623550676">"Eralda"</string>
+ <string name="action_split" msgid="2098009717623550676">"Jaga pooleks"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Salvesta rakendusepaar"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Jagatud ekraanikuva kasutamiseks puudutage muud rakendust"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Valige jagatud ekraanikuva jaoks muu rakendus."</string>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index b0694c1..040976d 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Järjestelmän navigointiasetukset"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaa"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Kuvakaappaus"</string>
- <string name="action_split" msgid="2098009717623550676">"Jaa"</string>
+ <string name="action_split" msgid="2098009717623550676">"Jaettu näyttö"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Tallenna pari"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Avaa jaettu näyttö napauttamalla toista sovellusta"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Käytä jaettua näyttöä valitsemalla toinen sovellus"</string>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index e696faf..ecde5a3 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
- <string name="action_split" msgid="2098009717623550676">"ခွဲထုတ်ရန်"</string>
+ <string name="action_split" msgid="2098009717623550676">"ခွဲရန်"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"အက်ပ်အတွဲ သိမ်းရန်"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"မျက်နှာပြင် ခွဲ၍ပြသရန် အက်ပ်နောက်တစ်ခုကို တို့ပါ"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသုံးရန် နောက်အက်ပ်တစ်ခုရွေးပါ"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 1086f9f..727f3e7 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -95,7 +95,7 @@
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系统导航设置"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
<string name="action_screenshot" msgid="8171125848358142917">"屏幕截图"</string>
- <string name="action_split" msgid="2098009717623550676">"拆分"</string>
+ <string name="action_split" msgid="2098009717623550676">"分屏"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"保存应用组合"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"点按另一个应用即可使用分屏"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"另外选择一个应用才可使用分屏模式"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c5f25ad..aea4602 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -456,8 +456,10 @@
<!-- Bubble bar drop target -->
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
- <dimen name="bubble_expanded_view_drop_target_corner_radius">16dp</dimen>
- <dimen name="bubble_expanded_view_drop_target_width">412dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_default_width">412dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_default_height">600dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_corner_radius">28dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_padding">24dp</dimen>
<dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Launcher splash screen -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 0697f47..72aaa90 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -58,6 +58,7 @@
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -1881,7 +1882,7 @@
return new ContainerAnimationRunner(
new ActivityTransitionAnimator.AnimationDelegate(
- controller, callback, listener));
+ MAIN_EXECUTOR, controller, callback, listener));
}
/**
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 70e01f5..a16031d 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -286,7 +286,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "PredictionRowView");
writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index ae8848f..de974ec 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -533,7 +533,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "HotseatPredictionController");
writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 65a49bd..8b5ed7c 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -73,6 +73,7 @@
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
@@ -150,7 +151,8 @@
// TODO: Implement caching and preloading
WorkspaceItemFactory factory =
- new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId);
+ new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
+ state.containerId);
FixedContainerItems fci = new FixedContainerItems(state.containerId,
state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
@@ -530,6 +532,7 @@
private final LauncherAppState mAppState;
private final UserManagerState mUMS;
+ private final PackageManagerHelper mPmHelper;
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
private final int mMaxCount;
private final int mContainer;
@@ -537,9 +540,11 @@
private int mReadCount = 0;
protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) {
+ PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
+ int maxCount, int container) {
mAppState = appState;
mUMS = ums;
+ mPmHelper = pmHelper;
mPinnedShortcuts = pinnedShortcuts;
mMaxCount = maxCount;
mContainer = container;
@@ -563,6 +568,7 @@
lai,
UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
ApiWrapper.INSTANCE.get(mAppState.getContext()),
+ mPmHelper,
mUMS.isUserQuiet(user));
info.container = mContainer;
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index 184ea71..fe9ade9 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -25,7 +25,7 @@
/** {@link SystemShortcut.Factory} implementation to create workspace split shortcuts */
public interface QuickstepSystemShortcut {
- String TAG = QuickstepSystemShortcut.class.getSimpleName();
+ String TAG = "QuickstepSystemShortcut";
static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
SplitPositionOption position) {
diff --git a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
index 2b6f77f..c94edce 100644
--- a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
+++ b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt
@@ -45,7 +45,7 @@
) : SystemShortcut<T>(iconResId, labelResId, target, itemInfo, originalView) where
T : Context?,
T : ActivityContext? {
- private val TAG = SystemShortcut::class.java.simpleName
+ private val TAG = "SplitShortcut"
// Initiate splitscreen from the Home screen or Home All Apps
protected val splitSelectSource: SplitSelectSource?
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 882682d..747612d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -182,7 +182,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + this.getClass().getSimpleName());
+ writer.println(prefix + "DepthController");
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
writer.println(prefix + "\tmSurface=" + mSurface);
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index c0ecc61..06d9ee6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -133,4 +133,9 @@
protected TISBindHelper getTISBindHelper() {
return mRecentsActivity.getTISBindHelper();
}
+
+ @Override
+ protected String getTaskbarUIControllerName() {
+ return "FallbackTaskbarUIController";
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 2ce6a41..4f02122 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -446,4 +446,9 @@
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
}
+
+ @Override
+ protected String getTaskbarUIControllerName() {
+ return "LauncherTaskbarUIController";
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
index 83e4571..94e2244 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -15,9 +15,12 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
@@ -111,4 +114,17 @@
setBackgroundColor(newColor);
}
}
+
+ /**
+ * Updates the handle scale.
+ *
+ * @param scale target scale to animate towards (starting from current scale)
+ * @param durationMs milliseconds for the animation to take
+ */
+ public void animateScale(float scale, long durationMs) {
+ ObjectAnimator scaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofFloat(SCALE_PROPERTY, scale));
+ scaleAnim.setDuration(durationMs).setAutoCancel(true);
+ scaleAnim.start();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index f258b47..36e054a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -57,6 +57,10 @@
public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3;
private static final int NUM_ALPHA_CHANNELS = 4;
+ // Values for long press animations, picked to most closely match navbar spec.
+ private static final float SCALE_TOUCH_ANIMATION_SHRINK = 0.85f;
+ private static final float SCALE_TOUCH_ANIMATION_EXPAND = 1.18f;
+
/**
* The SharedPreferences key for whether the stashed handle region is dark.
*/
@@ -324,7 +328,13 @@
@Override
public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
- // TODO(b/308693847): Animate similarly to NavigationHandle.java (SysUI).
+ float targetScale;
+ if (isTouchDown) {
+ targetScale = shrink ? SCALE_TOUCH_ANIMATION_SHRINK : SCALE_TOUCH_ANIMATION_EXPAND;
+ } else {
+ targetScale = 1f;
+ }
+ mStashedHandleView.animateScale(targetScale, durationMs);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e5396ee..af053e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -260,9 +260,9 @@
new BubbleDragController(this),
new BubbleDismissController(this, mDragLayer),
new BubbleBarPinController(this, mDragLayer,
- () -> getDeviceProfile().getDisplayInfo().currentSize),
+ () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
new BubblePinController(this, mDragLayer,
- () -> getDeviceProfile().getDisplayInfo().currentSize)
+ () -> DisplayController.INSTANCE.get(this).getInfo().currentSize)
));
}
@@ -965,7 +965,9 @@
mWindowLayoutParams.paramsForRotation[rot].height = size;
}
}
- mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ mControllers.runAfterInit(
+ mControllers.taskbarInsetsController
+ ::onTaskbarOrBubblebarWindowHeightOrInsetsChanged);
notifyUpdateLayoutParams();
}
@@ -1451,7 +1453,7 @@
});
}
- protected boolean isUserSetupComplete() {
+ public boolean isUserSetupComplete() {
return mIsUserSetupComplete;
}
@@ -1487,7 +1489,8 @@
((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation(
fullAnimation, duration);
}
- mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
+ mControllers.taskbarStashController.addUnstashToHotseatAnimationFromSuw(fullAnimation,
+ duration);
View allAppsButton = mControllers.taskbarViewController.getAllAppsButtonView();
if (allAppsButton != null && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index ee21eac..6ea52cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -65,7 +65,7 @@
*/
public class TaskbarLauncherStateController {
- private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
+ private static final String TAG = "TaskbarLauncherStateController";
private static final boolean DEBUG = false;
/** Launcher activity is visible and focused. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index ade4649..13a68a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -66,7 +66,7 @@
/** Allow some time in between the long press for back and recents. */
static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
- private static final String TAG = TaskbarNavButtonController.class.getSimpleName();
+ private static final String TAG = "TaskbarNavButtonController";
private long mLastScreenPinLongPress;
private boolean mScreenPinned;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 182ff7e..f764a83 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -78,7 +78,7 @@
* create a cohesive animation between stashed/unstashed states.
*/
public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
- private static final String TAG = TaskbarStashController.class.getSimpleName();
+ private static final String TAG = "TaskbarStashController";
private static final boolean DEBUG = false;
public static final int FLAG_IN_APP = 1 << 0;
@@ -355,7 +355,6 @@
boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
- updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneGestureNavMode());
applyState(hideTaskbar ? 0 : getStashDuration());
}
@@ -547,11 +546,12 @@
* sub-animations are properly coordinated. This duration should not
* actually be used since this animation tracks a swipe progress.
*/
- protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+ protected void addUnstashToHotseatAnimationFromSuw(AnimatorSet animation,
+ int placeholderDuration) {
// Defer any UI updates now to avoid the UI becoming stale when the animation plays.
mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
createAnimToIsStashed(
- /* isStashed= */ false,
+ /* isStashed= */ mActivity.isPhoneMode(),
placeholderDuration,
TRANSITION_UNSTASH_SUW_MANUAL,
/* jankTag= */ "SUW_MANUAL");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 2e78489..6abd5a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -44,7 +44,7 @@
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import java.io.PrintWriter;
@@ -55,7 +55,6 @@
* Base class for providing different taskbar UI
*/
public class TaskbarUIController {
-
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
// Initialized in init.
@@ -91,6 +90,10 @@
*/
protected void onIconLayoutBoundsChanged() { }
+ protected String getTaskbarUIControllerName() {
+ return "TaskbarUIController";
+ }
+
/** Called when an icon is launched. */
@CallSuper
public void onTaskbarIconLaunched(ItemInfo item) {
@@ -207,7 +210,7 @@
pw.println(String.format(
"%sTaskbarUIController: using an instance of %s",
prefix,
- getClass().getSimpleName()));
+ getTaskbarUIControllerName()));
}
/**
@@ -259,14 +262,14 @@
if (foundTaskView != null) {
// There is already a running app of this type, use that as second app.
// Get index of task (0 or 1), in case it's a GroupedTaskView
- TaskIdAttributeContainer taskAttributes =
- foundTaskView.getTaskAttributesById(foundTask.key.id);
+ TaskContainer taskContainer =
+ foundTaskView.getTaskContainerById(foundTask.key.id);
recents.confirmSplitSelect(
foundTaskView,
foundTask,
- taskAttributes.getIconView().getDrawable(),
- taskAttributes.getThumbnailView(),
- taskAttributes.getThumbnailView().getThumbnail(),
+ taskContainer.getIconView().getDrawable(),
+ taskContainer.getThumbnailView(),
+ taskContainer.getThumbnailView().getThumbnail(),
null /* intent */,
null /* user */,
info);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 77f8a8a..df7a7ba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -71,7 +71,7 @@
*/
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
DeviceProfile.OnDeviceProfileChangeListener {
- private static final String TAG = TaskbarView.class.getSimpleName();
+ private static final String TAG = "TaskbarView";
private static final Rect sTmpRect = new Rect();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e0b446e..0ee3d7f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -79,7 +79,7 @@
*/
public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController {
- private static final String TAG = TaskbarViewController.class.getSimpleName();
+ private static final String TAG = "TaskbarViewController";
private static final Runnable NO_OP = () -> { };
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 90c3ea7..a59e81b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -158,6 +158,7 @@
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
arrowDrawable.alpha = alpha
+ invalidateSelf()
}
override fun getAlpha(): Int {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 66e5302..5789f0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -94,7 +94,7 @@
*/
public class BubbleBarController extends IBubblesListener.Stub {
- private static final String TAG = BubbleBarController.class.getSimpleName();
+ private static final String TAG = "BubbleBarController";
private static final boolean DEBUG = false;
/**
@@ -150,6 +150,7 @@
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
+ private BubblePinController mBubblePinController;
// Keep track of bubble bar bounds sent to shell to avoid sending duplicate updates
private final Rect mLastSentBubbleBarBounds = new Rect();
@@ -169,6 +170,7 @@
BubbleBarLocation bubbleBarLocation;
List<RemovedBubble> removedBubbles;
List<String> bubbleKeysInOrder;
+ Point expandedViewDropTargetSize;
// These need to be loaded in the background
BubbleBarBubble addedBubble;
@@ -186,6 +188,7 @@
bubbleBarLocation = update.bubbleBarLocation;
removedBubbles = update.removedBubbles;
bubbleKeysInOrder = update.bubbleKeysInOrder;
+ expandedViewDropTargetSize = update.expandedViewDropTargetSize;
}
}
@@ -216,6 +219,7 @@
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
+ mBubblePinController = bubbleControllers.bubblePinController;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(
@@ -403,9 +407,6 @@
}
if (bubbleToSelect != null) {
setSelectedBubbleInternal(bubbleToSelect);
- if (previouslySelectedBubble == null) {
- mBubbleStashController.animateToInitialState(update.expanded);
- }
}
if (update.shouldShowEducation) {
mBubbleBarViewController.prepareToShowEducation();
@@ -422,6 +423,9 @@
updateBubbleBarLocationInternal(update.bubbleBarLocation);
}
}
+ if (update.expandedViewDropTargetSize != null) {
+ mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize);
+ }
}
/** Tells WMShell to show the currently selected bubble. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index de93ba5..d08015e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -76,7 +76,7 @@
*/
public class BubbleBarView extends FrameLayout {
- private static final String TAG = BubbleBarView.class.getSimpleName();
+ private static final String TAG = "BubbleBarView";
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
@@ -98,23 +98,6 @@
private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
/**
- * Custom property to set translationX value for the bar view while a bubble is being dragged.
- * Skips applying translation to the dragged bubble.
- */
- private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_TRANSLATION_X =
- new FloatProperty<>("bubbleDragTranslationX") {
- @Override
- public void setValue(BubbleBarView bubbleBarView, float translationX) {
- bubbleBarView.setTranslationXDuringBubbleDrag(translationX);
- }
-
- @Override
- public Float get(BubbleBarView bubbleBarView) {
- return bubbleBarView.mTranslationXDuringDrag;
- }
- };
-
- /**
* Custom property to set alpha value for the bar view while a bubble is being dragged.
* Skips applying alpha to the dragged bubble.
*/
@@ -189,8 +172,7 @@
@Nullable
private BubbleView mDraggedBubbleView;
- private float mTranslationXDuringDrag = 0f;
- private float mAlphaDuringDrag = 0f;
+ private float mAlphaDuringDrag = 1f;
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
@@ -264,6 +246,17 @@
});
}
+ @Override
+ public void setTranslationX(float translationX) {
+ super.setTranslationX(translationX);
+ if (mDraggedBubbleView != null) {
+ // Apply reverse of the translation as an offset to the dragged view. This ensures
+ // that the dragged bubble stays at the current location on the screen and its
+ // position is not affected by the parent translation.
+ mDraggedBubbleView.setOffsetX(-translationX);
+ }
+ }
+
/**
* Sets new icon size and spacing between icons and bubble bar borders.
*
@@ -322,11 +315,9 @@
final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
mBubbleBarBackground.setAnchorLeft(onLeft);
mRelativePivotX = onLeft ? 0f : 1f;
- if (getLayoutParams() instanceof LayoutParams lp) {
- lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
- setLayoutParams(lp);
- }
- invalidate();
+ LayoutParams lp = (LayoutParams) getLayoutParams();
+ lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
+ setLayoutParams(lp); // triggers a relayout
}
/**
@@ -340,11 +331,6 @@
* Update {@link BubbleBarLocation}
*/
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
- if (mBubbleBarLocationAnimator != null) {
- mBubbleBarLocationAnimator.removeAllListeners();
- mBubbleBarLocationAnimator.cancel();
- mBubbleBarLocationAnimator = null;
- }
resetDragAnimation();
if (bubbleBarLocation != mBubbleBarLocation) {
mBubbleBarLocation = bubbleBarLocation;
@@ -361,6 +347,10 @@
}
mDragging = dragging;
setElevation(dragging ? mDragElevation : mBubbleElevation);
+ if (!mDragging) {
+ // Relayout after dragging to ensure that the dragged bubble is positioned correctly
+ requestLayout();
+ }
}
/**
@@ -459,14 +449,13 @@
}
private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) {
- final FloatProperty<? super BubbleBarView> txProp = getLocationAnimTranslationXProperty();
final float shift =
getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
- final float tx = txProp.get(this) + (onLeft ? -shift : shift);
+ final float tx = getTranslationX() + (onLeft ? -shift : shift);
- ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, txProp, tx).setDuration(
- FADE_OUT_ANIM_POSITION_DURATION_MS);
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, VIEW_TRANSLATE_X, tx)
+ .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f)
@@ -505,7 +494,7 @@
.setEndValue(finalTx)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
- .build(this, getLocationAnimTranslationXProperty());
+ .build(this, VIEW_TRANSLATE_X);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f)
.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
@@ -516,15 +505,6 @@
}
/**
- * Get property that can be used to animate the translation-x value for the bar.
- * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_TRANSLATION_X}.
- * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_TRANSLATE_X} otherwise.
- */
- private FloatProperty<? super BubbleBarView> getLocationAnimTranslationXProperty() {
- return mDraggedBubbleView == null ? VIEW_TRANSLATE_X : BUBBLE_DRAG_TRANSLATION_X;
- }
-
- /**
* Get property that can be used to animate the alpha value for the bar.
* When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
* Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_ALPHA} otherwise.
@@ -534,31 +514,6 @@
}
/**
- * Set translation-x value for the bar while a bubble is being dragged.
- * We can not update translation on the bar directly because the dragged bubble would be
- * affected as well. As it is a child view.
- * Instead, while a bubble is being dragged, set translation on each child view, that is not the
- * dragged view. And set a translation on the background.
- * This allows for the dragged bubble view to remain in position while the bar moves during
- * animation.
- */
- private void setTranslationXDuringBubbleDrag(float translationX) {
- mTranslationXDuringDrag = translationX;
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- BubbleView view = (BubbleView) getChildAt(i);
- if (view != mDraggedBubbleView) {
- view.setBubbleBarTranslationX(translationX);
- }
- }
- if (mBubbleBarBackground != null) {
- mTempBackgroundBounds.set(mBubbleBarBackground.getBounds());
- mTempBackgroundBounds.offsetTo((int) translationX, 0);
- mBubbleBarBackground.setBounds(mTempBackgroundBounds);
- }
- }
-
- /**
* Set alpha value for the bar while a bubble is being dragged.
* We can not update the alpha on the bar directly because the dragged bubble would be affected
* as well. As it is a child view.
@@ -587,7 +542,6 @@
mBubbleBarLocationAnimator.cancel();
mBubbleBarLocationAnimator = null;
}
- setTranslationXDuringBubbleDrag(0f);
setAlphaDuringBubbleDrag(1f);
setTranslationX(0f);
setAlpha(1f);
@@ -706,6 +660,10 @@
// Skip the dragged bubble. Its translation is managed by the drag controller.
continue;
}
+ // Clear out drag translation and offset
+ bv.setDragTranslationX(0f);
+ bv.setOffsetX(0f);
+
bv.setTranslationY(ty);
// the position of the bubble when the bar is fully expanded
@@ -851,10 +809,6 @@
public void setDraggedBubble(@Nullable BubbleView view) {
if (mDraggedBubbleView != null) {
mDraggedBubbleView.setZ(0);
- if (view == null) {
- // We are clearing the dragged bubble, reset drag
- resetDragAnimation();
- }
}
mDraggedBubbleView = view;
if (view != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 0b74e15..cd8eaf9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -55,7 +55,7 @@
*/
public class BubbleBarViewController {
- private static final String TAG = BubbleBarViewController.class.getSimpleName();
+ private static final String TAG = "BubbleBarViewController";
private static final float APP_ICON_SMALL_DP = 44f;
private static final float APP_ICON_MEDIUM_DP = 48f;
private static final float APP_ICON_LARGE_DP = 52f;
@@ -406,10 +406,21 @@
return;
}
+ if (!(b instanceof BubbleBarBubble bubble)) {
+ return;
+ }
+
boolean isInApp = mTaskbarStashController.isInApp();
+ // if this is the first bubble, animate to the initial state. one bubble is the overflow
+ // so check for at most 2 children.
+ if (mBarView.getChildCount() <= 2) {
+ mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
+ return;
+ }
+
// only animate the new bubble if we're in an app and not auto expanding
- if (b instanceof BubbleBarBubble && isInApp && !isExpanding && !isExpanded()) {
- mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
+ if (isInApp && !isExpanding && !isExpanded()) {
+ mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
}
} else {
Log.w(TAG, "addBubble, bubble was null!");
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
index a40f33c..0e6fa3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -40,7 +40,7 @@
* @see BubbleDragController
*/
public class BubbleDismissController {
- private static final String TAG = BubbleDismissController.class.getSimpleName();
+ private static final String TAG = "BubbleDismissController";
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mDragLayer;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
index 49f114a..287e906 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
@@ -60,6 +60,7 @@
private final float mBubbleFocusedScale;
private final float mBubbleCapturedScale;
private final float mDismissCapturedScale;
+ private final FloatPropertyCompat<View> mTranslationXProperty;
/**
* Should be initialised for each dragged view
@@ -81,9 +82,28 @@
if (view instanceof BubbleBarView) {
mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
mBubbleCapturedScale = mDismissCapturedScale;
+ mTranslationXProperty = DynamicAnimation.TRANSLATION_X;
} else {
mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
+ // Wrap BubbleView.DRAG_TRANSLATION_X as it can't be cast to FloatPropertyCompat<View>
+ mTranslationXProperty = new FloatPropertyCompat<>(
+ BubbleView.DRAG_TRANSLATION_X.getName()) {
+ @Override
+ public float getValue(View object) {
+ if (object instanceof BubbleView bubbleView) {
+ return BubbleView.DRAG_TRANSLATION_X.get(bubbleView);
+ }
+ return 0;
+ }
+
+ @Override
+ public void setValue(View object, float value) {
+ if (object instanceof BubbleView bubbleView) {
+ BubbleView.DRAG_TRANSLATION_X.setValue(bubbleView, value);
+ }
+ }
+ };
}
}
@@ -120,7 +140,7 @@
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, 1f)
.spring(DynamicAnimation.SCALE_Y, 1f)
- .spring(DynamicAnimation.TRANSLATION_X, restingPosition.x, velocity.x,
+ .spring(mTranslationXProperty, restingPosition.x, velocity.x,
mTranslationConfig)
.spring(DynamicAnimation.TRANSLATION_Y, restingPosition.y, velocity.y,
mTranslationConfig)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index 1764f75..15de1b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -104,7 +104,9 @@
}
@Override
- protected void onDragUpdate(float x, float y) {
+ protected void onDragUpdate(float x, float y, float newTx, float newTy) {
+ bubbleView.setDragTranslationX(newTx);
+ bubbleView.setTranslationY(newTy);
mBubblePinController.onDragUpdate(x, y);
}
@@ -145,12 +147,7 @@
private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
private final LocationChangeListener mLocationChangeListener =
- new LocationChangeListener() {
- @Override
- public void onRelease(@NonNull BubbleBarLocation location) {
- mReleasedLocation = location;
- }
- };
+ location -> mReleasedLocation = location;
@Override
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
@@ -172,7 +169,9 @@
}
@Override
- protected void onDragUpdate(float x, float y) {
+ protected void onDragUpdate(float x, float y, float newTx, float newTy) {
+ bubbleBarView.setTranslationX(newTx);
+ bubbleBarView.setTranslationY(newTy);
mBubbleBarPinController.onDragUpdate(x, y);
}
@@ -265,8 +264,7 @@
* Called when bubble is dragged to new coordinates.
* Not called while bubble is stuck to the dismiss target.
*/
- protected void onDragUpdate(float x, float y) {
- }
+ protected abstract void onDragUpdate(float x, float y, float newTx, float newTy);
/**
* Called when the dragging interaction has ended and all the animations have completed
@@ -411,9 +409,9 @@
private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy,
float x, float y) {
if (mBubbleDismissController.handleTouchEvent(event)) return;
- view.setTranslationX(mViewInitialPosition.x + dx);
- view.setTranslationY(mViewInitialPosition.y + dy);
- onDragUpdate(x, y);
+ final float newTx = mViewInitialPosition.x + dx;
+ final float newTy = mViewInitialPosition.y + dy;
+ onDragUpdate(x, y, newTx, newTy);
}
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
index fef7fa1..a77e685 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
@@ -37,12 +37,17 @@
screenSizeProvider: () -> Point
) : BaseBubblePinController(screenSizeProvider) {
+ var dropTargetSize: Point? = null
+
private lateinit var bubbleBarViewController: BubbleBarViewController
private lateinit var bubbleStashController: BubbleStashController
private var exclRectWidth: Float = 0f
private var exclRectHeight: Float = 0f
private var dropTargetView: View? = null
+ // Fallback width and height in case shell has not sent the size over
+ private var dropTargetDefaultWidth: Int = 0
+ private var dropTargetDefaultHeight: Int = 0
private var dropTargetMargin: Int = 0
fun init(bubbleControllers: BubbleControllers) {
@@ -50,6 +55,14 @@
bubbleStashController = bubbleControllers.bubbleStashController
exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
+ dropTargetDefaultWidth =
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_drop_target_default_width
+ )
+ dropTargetDefaultHeight =
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_drop_target_default_height
+ )
dropTargetMargin =
context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin)
}
@@ -75,7 +88,6 @@
return LayoutInflater.from(context)
.inflate(R.layout.bubble_expanded_view_drop_target, container, false)
.also { view ->
- // TODO(b/330585402): dynamic height for the drop target based on actual height
dropTargetView = view
container.addView(view)
}
@@ -88,6 +100,8 @@
val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds
dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
+ width = dropTargetSize?.x ?: dropTargetDefaultWidth
+ height = dropTargetSize?.y ?: dropTargetDefaultHeight
bottomMargin =
-bubbleStashController.bubbleBarTranslationY.toInt() +
bubbleBarBounds.height() +
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 4b3416c..5d01b9b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -42,7 +42,7 @@
*/
public class BubbleStashController {
- private static final String TAG = BubbleStashController.class.getSimpleName();
+ private static final String TAG = "BubbleStashController";
/**
* How long to stash/unstash.
@@ -123,21 +123,17 @@
}
/**
- * Animates the bubble bar and handle to their initial state, transitioning from the state where
- * both views are invisible. Called when the first bubble is added or when the device is
+ * Animates the handle (or bubble bar depending on state) to be visible after the device is
* unlocked.
*
* <p>Normally either the bubble bar or the handle is visible,
* and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to transition
* between these two states. But the transition from the state where both the bar and handle
* are invisible is slightly different.
- *
- * <p>The initial state will depend on the current state of the device, i.e. overview, home etc
- * and whether bubbles are requested to be expanded.
*/
- public void animateToInitialState(boolean expanding) {
+ private void animateAfterUnlock() {
AnimatorSet animatorSet = new AnimatorSet();
- if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
+ if (mBubblesShowingOnHome || mBubblesShowingOnOverview) {
mIsStashed = false;
animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
@@ -217,7 +213,7 @@
if (isSysuiLocked != mIsSysuiLocked) {
mIsSysuiLocked = isSysuiLocked;
if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
- animateToInitialState(false /* expanding */);
+ animateAfterUnlock();
}
}
}
@@ -453,4 +449,9 @@
mIsStashed = isStashed;
onIsStashedChanged();
}
+
+ /** Set the translation Y for the stashed handle. */
+ public void setHandleTranslationY(float ty) {
+ mHandleViewController.setTranslationYForSwipe(ty);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 3dc4ebc..61a6bce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -22,11 +22,13 @@
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
@@ -47,6 +49,25 @@
public static final int DEFAULT_PATH_SIZE = 100;
/**
+ * Property to update drag translation value.
+ *
+ * @see BubbleView#getDragTranslationX()
+ * @see BubbleView#setDragTranslationX(float)
+ */
+ public static final FloatProperty<BubbleView> DRAG_TRANSLATION_X = new FloatProperty<>(
+ "dragTranslationX") {
+ @Override
+ public void setValue(@NonNull BubbleView bubbleView, float value) {
+ bubbleView.setDragTranslationX(value);
+ }
+
+ @Override
+ public Float get(BubbleView bubbleView) {
+ return bubbleView.getDragTranslationX();
+ }
+ };
+
+ /**
* Flags that suppress the visibility of the 'new' dot or the app badge, for one reason or
* another. If any of these flags are set, the dot will not be shown.
* If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
@@ -66,8 +87,8 @@
private final ImageView mAppIcon;
private final int mBubbleSize;
- private float mBubbleBarTranslationX = 0f;
- private float mTranslationX = 0f;
+ private float mDragTranslationX;
+ private float mOffsetX;
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
@@ -129,33 +150,37 @@
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
- @Override
- public void setTranslationX(float translationX) {
- // Overriding setting translationX as it can be a combination of the parent translation
- // and current view translation.
- // When a BubbleView is being dragged to pin the bubble bar to other side, we animate the
- // bar to the new location during the drag.
- // One part of the animation is updating the translation of the bubble bar. But doing
- // that also updates the translation for the child views, like the dragged bubble.
- // To get around that, we instead apply translation on each child view of bubble bar. It
- // is applied as bubble bar translation. This results in BubbleView's translation being a
- // sum of the translation it has and the parent bubble bar translation.
- mTranslationX = translationX;
- applyTranslation();
+ /**
+ * Set translation-x while this bubble is being dragged.
+ * Translation applied to the view is a sum of {@code translationX} and offset defined by
+ * {@link #setOffsetX(float)}.
+ */
+ public void setDragTranslationX(float translationX) {
+ mDragTranslationX = translationX;
+ applyDragTranslation();
}
/**
- * Translation of the bubble bar that hosts this bubble.
- * Is applied together with translation applied on the view through
- * {@link #setTranslationX(float)}.
+ * Get translation value applied via {@link #setDragTranslationX(float)}.
*/
- void setBubbleBarTranslationX(float translationX) {
- mBubbleBarTranslationX = translationX;
- applyTranslation();
+ public float getDragTranslationX() {
+ return mDragTranslationX;
}
- private void applyTranslation() {
- super.setTranslationX(mTranslationX + mBubbleBarTranslationX);
+ /**
+ * Set offset on x-axis while dragging.
+ * Used to counter parent translation in order to keep the dragged view at the current position
+ * on screen.
+ * Translation applied to the view is a sum of {@code offsetX} and translation defined by
+ * {@link #setDragTranslationX(float)}
+ */
+ public void setOffsetX(float offsetX) {
+ mOffsetX = offsetX;
+ applyDragTranslation();
+ }
+
+ private void applyDragTranslation() {
+ setTranslationX(mDragTranslationX + mOffsetX);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 66521c1..be935d8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -93,15 +93,15 @@
if (animator.isRunning()) animator.cancel()
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
// and the second part hides it after a delay.
- val showAnimation = buildShowAnimation()
- val hideAnimation = buildHideAnimation()
+ val showAnimation = buildHandleToBubbleBarAnimation()
+ val hideAnimation = buildBubbleBarToHandleAnimation()
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
scheduler.post(showAnimation)
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
/**
- * Returns a [Runnable] that starts the animation that shows the new or updated bubble.
+ * Returns a [Runnable] that starts the animation that morphs the handle to the bubble bar.
*
* Visually, the animation is divided into 2 parts. The stash handle starts animating up and
* fading out and then the bubble bar starts animating up and fading in.
@@ -114,7 +114,7 @@
* 3. The third part is the overshoot of the spring animation, where we make the bubble fully
* visible which helps avoiding further updates when we re-enter the second part.
*/
- private fun buildShowAnimation() = Runnable {
+ private fun buildHandleToBubbleBarAnimation() = Runnable {
// prepare the bubble bar for the animation
bubbleBarView.onAnimatingBubbleStarted()
bubbleBarView.visibility = VISIBLE
@@ -197,7 +197,8 @@
}
/**
- * Returns a [Runnable] that starts the animation that hides the bubble bar.
+ * Returns a [Runnable] that starts the animation that hides the bubble bar and morphs it into
+ * the stashed handle.
*
* Similarly to the show animation, this is visually divided into 2 parts. We first animate the
* bubble bar out, and then animate the stash handle in. At the end of the animation we reset
@@ -209,13 +210,14 @@
* 2. In the second part the bubble bar is fully hidden and the handle animates in.
* 3. The third part is the overshoot. The handle is made fully visible.
*/
- private fun buildHideAnimation() = Runnable {
+ private fun buildBubbleBarToHandleAnimation() = Runnable {
if (animatingBubble == null) return@Runnable
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
val stashedHandleTranslationY =
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
// this is the total distance that both the stashed handle and the bar will be traveling
val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
+ bubbleStashController.setHandleTranslationY(totalTranslationY)
val animator = bubbleStashController.stashedHandlePhysicsAnimator
animator.setDefaultSpringConfig(springConfig)
animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
@@ -259,6 +261,50 @@
animator.start()
}
+ /** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
+ fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
+ val bubbleView = b.view
+ val animator = PhysicsAnimator.getInstance(bubbleView)
+ if (animator.isRunning()) animator.cancel()
+ // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
+ // and the second part hides it after a delay if we are in an app.
+ val showAnimation = buildBubbleBarBounceAnimation()
+ val hideAnimation =
+ if (isInApp && !isExpanding) {
+ buildBubbleBarToHandleAnimation()
+ } else {
+ // in this case the bubble bar remains visible so not much to do. once we implement
+ // the flyout we'll update this runnable to hide it.
+ Runnable {
+ animatingBubble = null
+ bubbleStashController.showBubbleBarImmediate()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ }
+ }
+ animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
+ scheduler.post(showAnimation)
+ scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
+ }
+
+ private fun buildBubbleBarBounceAnimation() = Runnable {
+ // prepare the bubble bar for the animation
+ bubbleBarView.onAnimatingBubbleStarted()
+ bubbleBarView.translationY = bubbleBarView.height.toFloat()
+ bubbleBarView.visibility = VISIBLE
+ bubbleBarView.alpha = 1f
+ bubbleBarView.scaleX = 1f
+ bubbleBarView.scaleY = 1f
+
+ val animator = PhysicsAnimator.getInstance(bubbleBarView)
+ animator.setDefaultSpringConfig(springConfig)
+ animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY)
+ animator.addEndListener { _, _, _, _, _, _, _ ->
+ // the bubble bar is now fully settled in. update taskbar touch region so it's touchable
+ bubbleStashController.updateTaskbarTouchRegion()
+ }
+ animator.start()
+ }
+
/** Handles clicking on the animating bubble while the animation is still playing. */
fun onBubbleClickedWhileAnimating() {
val hideAnimation = animatingBubble?.hideAnimation ?: return
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 8ad2493..e487f9f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -24,8 +24,10 @@
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
/**
@@ -73,6 +75,23 @@
return params
}
+ fun adjustForSetupInPhoneMode(
+ navButtonsLayoutParams: FrameLayout.LayoutParams,
+ navButtonsViewLayoutParams: FrameLayout.LayoutParams,
+ deviceProfile: DeviceProfile
+ ) {
+ val phoneOrPortraitSetupMargin =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
+ navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
+ navButtonsLayoutParams.bottomMargin =
+ if (!deviceProfile.isLandscape) 0
+ else
+ phoneOrPortraitSetupMargin -
+ resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
+ navButtonsViewLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
+ }
+
open fun repositionContextualContainer(
contextualContainer: ViewGroup,
buttonSize: Int,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 1e9f09b..2497fbb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -116,6 +116,7 @@
isPhoneGestureMode -> {
PhoneGestureLayoutter(
resources,
+ navButtonsView,
navButtonContainer,
endContextualContainer,
startContextualContainer,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 8d91f2c..390ec34 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -17,15 +17,19 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Gravity
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
+import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.TaskbarActivityContext
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
class PhoneGestureLayoutter(
resources: Resources,
+ navButtonsView: NearestTouchFrame,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
@@ -42,8 +46,31 @@
a11yButton,
space
) {
+ private val mNavButtonsView = navButtonsView
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+ // TODO: look into if we should use SetupNavLayoutter instead.
+ if (!context.isUserSetupComplete) {
+ // Since setup wizard only has back button enabled, it looks strange to be
+ // end-aligned, so start-align instead.
+ val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
+ val navButtonsViewLayoutParams =
+ mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+ val deviceProfile: DeviceProfile = context.deviceProfile
+
+ navButtonsLayoutParams.marginEnd = 0
+ navButtonsLayoutParams.gravity = Gravity.START
+ context.setTaskbarWindowSize(context.setupWindowSize)
+
+ adjustForSetupInPhoneMode(
+ navButtonsLayoutParams,
+ navButtonsViewLayoutParams,
+ deviceProfile
+ )
+ mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+ navButtonContainer.layoutParams = navButtonsLayoutParams
+ }
+
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 91042c3..22a3630 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -77,16 +77,11 @@
navButtonsLayoutParams.height =
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
} else {
- val phoneOrPortraitSetupMargin =
- resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
- navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
- navButtonsLayoutParams.bottomMargin =
- if (!deviceProfile.isLandscape) 0
- else
- phoneOrPortraitSetupMargin -
- resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
- navButtonsViewLayoutParams.height =
- resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
+ adjustForSetupInPhoneMode(
+ navButtonsLayoutParams,
+ navButtonsViewLayoutParams,
+ deviceProfile
+ )
}
mNavButtonsView.layoutParams = navButtonsViewLayoutParams
navButtonContainer.layoutParams = navButtonsLayoutParams
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 6c1d4b1..317e6f2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -50,6 +49,7 @@
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
/**
@@ -167,8 +167,8 @@
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
- propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
- MULTI_PROPERTY_VALUE, overviewButtonAlpha, config.getInterpolator(
+ propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlphaSetter(),
+ OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 527a776..fc0df76 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -248,7 +248,7 @@
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0);
mRecentsView.setContentAlpha(1);
mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
- mLauncher.getActionsView().getVisibilityAlpha().setValue(
+ mLauncher.getActionsView().getVisibilityAlphaSetter().accept(
(fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
mRecentsView.setTaskIconScaledDown(true);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 05a55d0..16185f5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -151,7 +151,7 @@
int sysuiFlags = 0;
TaskView tv = mOverviewPanel.getTaskViewAt(0);
if (tv != null) {
- sysuiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ sysuiFlags = tv.getFirstThumbnailView().getSysUiStatusNavFlags();
}
mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 300d697..26b528c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -252,7 +252,7 @@
mTaskBeingDragged, maxDuration, currentInterpolator);
// Since the thumbnail is what is filling the screen, based the end displacement on it.
- View thumbnailView = mTaskBeingDragged.getThumbnail();
+ View thumbnailView = mTaskBeingDragged.getFirstThumbnailView();
mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
mEndDisplacement = secondaryLayerDimension - mTempCords[1];
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 2ce40b4..28ae3d2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -135,10 +135,11 @@
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -147,6 +148,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.window.flags.Flags;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
@@ -922,7 +924,7 @@
TaskView runningTask = mRecentsView.getRunningTaskView();
TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
- : centermostTask.getThumbnail().getSysUiStatusNavFlags();
+ : centermostTask.getFirstThumbnailView().getSysUiStatusNavFlags();
boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
boolean quickswitchThresholdPassed = centermostTask != runningTask;
@@ -1258,13 +1260,16 @@
? mRecentsView.getNextPageTaskView() : null;
TaskView currentPageTaskView = mRecentsView != null
? mRecentsView.getCurrentPageTaskView() : null;
- if (((nextPageTaskView != null && nextPageTaskView.isDesktopTask())
- || (currentPageTaskView != null && currentPageTaskView.isDesktopTask()))
- && endTarget == NEW_TASK) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- return LAST_TASK;
- }
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if ((nextPageTaskView instanceof DesktopTaskView
+ || currentPageTaskView instanceof DesktopTaskView)
+ && endTarget == NEW_TASK) {
+ return LAST_TASK;
+ }
+ }
return endTarget;
}
@@ -1421,14 +1426,27 @@
mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
setClampScrollOffset(false);
};
- if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
- && !mRecentsView.getCurrentPageTaskView().isDesktopTask())) {
- ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
- .SET_ON_PAGE_TRANSITION_END_CALLBACK);
- // TODO(b/268075592): add support for quickswitch to/from desktop
- mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
+ && !(mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView))) {
+ ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
+ .SET_ON_PAGE_TRANSITION_END_CALLBACK);
+ mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+ } else {
+ onPageTransitionEnd.run();
+ }
} else {
- onPageTransitionEnd.run();
+ if (mRecentsView != null) {
+ ActiveGestureLog.INSTANCE.trackEvent(
+ ActiveGestureErrorDetector
+ .GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK);
+ mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd);
+ } else {
+ onPageTransitionEnd.run();
+ }
}
animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
@@ -2250,13 +2268,14 @@
mRecentsAnimationController, mRecentsAnimationTargets);
});
- if ((mRecentsView.getNextPageTaskView() != null
- && mRecentsView.getNextPageTaskView().isDesktopTask())
- || (mRecentsView.getCurrentPageTaskView() != null
- && mRecentsView.getCurrentPageTaskView().isDesktopTask())) {
- // TODO(b/268075592): add support for quickswitch to/from desktop
- mRecentsViewScrollLinked = false;
- return;
+ if (Flags.enableDesktopWindowingMode()
+ && !(Flags.enableDesktopWindowingWallpaperActivity()
+ && Flags.enableDesktopWindowingQuickSwitch())) {
+ if (mRecentsView.getNextPageTaskView() instanceof DesktopTaskView
+ || mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView) {
+ mRecentsViewScrollLinked = false;
+ return;
+ }
}
// Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
@@ -2287,15 +2306,15 @@
int[] taskIds = nextTask.getTaskIds();
ActiveGestureLog.CompoundString nextTaskLog = new ActiveGestureLog.CompoundString(
"Launching task: ");
- for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
- if (c == null) {
+ for (TaskContainer container : nextTask.getTaskContainers()) {
+ if (container == null) {
continue;
}
nextTaskLog
.append("[id: ")
- .append(c.getTask().key.id)
+ .append(container.getTask().key.id)
.append(", pkg: ")
- .append(c.getTask().key.getPackageName())
+ .append(container.getTask().key.getPackageName())
.append("] | ");
}
mGestureState.updateLastStartedTaskIds(taskIds);
@@ -2407,7 +2426,7 @@
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
- if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
+ if (taskView == null || !taskView.getFirstThumbnailView().shouldShowSplashView()) {
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index aab6aa1..e33ef7f 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -23,12 +23,12 @@
import com.android.launcher3.popup.SystemShortcut
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
class DesktopSystemShortcut(
container: RecentsViewContainer,
- private val mTaskContainer: TaskIdAttributeContainer,
+ private val mTaskContainer: TaskContainer,
abstractFloatingViewHelper: AbstractFloatingViewHelper
) :
SystemShortcut<RecentsViewContainer>(
@@ -59,7 +59,7 @@
return object : TaskShortcutFactory {
override fun getShortcuts(
container: RecentsViewContainer,
- taskContainer: TaskIdAttributeContainer
+ taskContainer: TaskContainer
): List<DesktopSystemShortcut>? {
return if (!DesktopModeStatus.canEnterDesktopMode(container.asContext())) null
else if (!taskContainer.task.isDockable) null
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index f68f793..0f844e1 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -154,7 +154,7 @@
}
companion object {
- val configHelper by lazy { DeviceConfigHelper(::DeviceConfigWrapper) }
+ @JvmStatic val configHelper by lazy { DeviceConfigHelper(::DeviceConfigWrapper) }
@JvmStatic fun get() = configHelper.config
}
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 4b5a15d..6a9f509 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -305,8 +305,8 @@
return null;
}
if (sourceTaskView == null
- || sourceTaskView.getTask() == null
- || sourceTaskView.getTask().key.getComponent() == null) {
+ || sourceTaskView.getFirstTask() == null
+ || sourceTaskView.getFirstTask().key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
@@ -323,8 +323,8 @@
}
return mContainer.getFirstMatchForAppClose(launchCookieItemId,
- sourceTaskView.getTask().key.getComponent().getPackageName(),
- UserHandle.of(sourceTaskView.getTask().key.userId),
+ sourceTaskView.getFirstTask().key.getComponent().getPackageName(),
+ UserHandle.of(sourceTaskView.getFirstTask().key.userId),
false /* supportsAllAppsState */);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4b4f914..54466f3 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,6 +33,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -399,7 +400,8 @@
&& (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
- && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0;
+ && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) == 0;
}
/**
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 0ad60b7..4d3fe41 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -111,7 +111,7 @@
* Holds the reference to SystemUI.
*/
public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
- private static final String TAG = SystemUiProxy.class.getSimpleName();
+ private static final String TAG = "SystemUiProxy";
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
new MainThreadInitializedObject<>(SystemUiProxy::new);
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index d32c7a6..1a46fb6 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -46,7 +46,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -59,7 +59,7 @@
public class TaskOverlayFactory implements ResourceBasedOverride {
public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
final ArrayList<SystemShortcut> shortcuts = new ArrayList<>();
final RecentsViewContainer container =
RecentsViewContainer.containerFromContext(taskView.getContext());
@@ -292,7 +292,7 @@
@Override
public void onClick(View view) {
- saveScreenshot(mThumbnailView.getTaskView().getTask());
+ saveScreenshot(mThumbnailView.getTaskView().getFirstTask());
dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 8df4bdd..a53d91f 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -58,14 +58,13 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@@ -78,7 +77,7 @@
public interface TaskShortcutFactory {
@Nullable
default List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
return null;
}
@@ -108,7 +107,7 @@
TaskShortcutFactory APP_INFO = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
TaskView taskView = taskContainer.getTaskView();
AppInfo.SplitAccessibilityInfo accessibilityInfo =
new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
@@ -176,7 +175,7 @@
private final LauncherEvent mLauncherEvent;
public FreeformSystemShortcut(int iconRes, int textRes, RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer, LauncherEvent launcherEvent) {
+ TaskContainer taskContainer, LauncherEvent launcherEvent) {
super(iconRes, textRes, container, taskContainer.getItemInfo(),
taskContainer.getTaskView());
mLauncherEvent = launcherEvent;
@@ -199,7 +198,7 @@
}
private void startActivity() {
- final Task.TaskKey taskKey = mTaskView.getTask().key;
+ final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
final int taskId = taskKey.id;
final ActivityOptions options = makeLaunchOptions(mTarget);
if (options != null) {
@@ -292,7 +291,7 @@
TaskShortcutFactory SPLIT_SELECT = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
DeviceProfile deviceProfile = container.getDeviceProfile();
final Task task = taskContainer.getTask();
final int intentFlags = task.key.baseIntent.getFlags();
@@ -327,7 +326,7 @@
@Nullable
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
DeviceProfile deviceProfile = container.getDeviceProfile();
final TaskView taskView = taskContainer.getTaskView();
final RecentsView recentsView = taskView.getRecentsView();
@@ -336,24 +335,15 @@
recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
boolean shouldShowActionsButtonInstead =
isLargeTileFocusedTask && isInExpectedScrollPosition;
- boolean hasUnpinnableApp = Arrays.stream(taskView.getTaskIdAttributeContainers())
- .anyMatch(att -> att != null && att.getItemInfo() != null
- && ((att.getItemInfo().runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
// No "save app pair" menu item if:
- // - app pairs feature is not enabled
// - we are in 3p launcher
- // - the task in question is a single task
- // - at least one app in app pair is unpinnable
// - the Overview Actions Button should be visible
- // - the task is not a GroupedTaskView
- if (!FeatureFlags.enableAppPairs()
- || !recentsView.supportsAppPairs()
- || !taskView.containsMultipleTasks()
- || hasUnpinnableApp
+ // - the task view is not a valid save-able split pair
+ if (!recentsView.supportsAppPairs()
|| shouldShowActionsButtonInstead
- || !(taskView instanceof GroupedTaskView)) {
+ || !recentsView.getSplitSelectController().getAppPairsController()
+ .canSaveAppPair(taskView)) {
return null;
}
@@ -375,7 +365,7 @@
TaskShortcutFactory FREE_FORM = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
final Task task = taskContainer.getTask();
if (!task.isDockable) {
return null;
@@ -401,7 +391,7 @@
TaskShortcutFactory PIN = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
if (!SystemUiProxy.INSTANCE.get(container.asContext()).isActive()) {
return null;
}
@@ -423,7 +413,7 @@
private final TaskView mTaskView;
public PinSystemShortcut(RecentsViewContainer target,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
taskContainer.getItemInfo(), taskContainer.getTaskView());
mTaskView = taskContainer.getTaskView();
@@ -433,7 +423,7 @@
public void onClick(View view) {
if (mTaskView.launchTaskAnimated() != null) {
SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
- mTaskView.getTask().key.id);
+ mTaskView.getFirstTask().key.id);
}
dismissTaskMenuView();
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
@@ -444,7 +434,7 @@
TaskShortcutFactory INSTALL = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
Task t = taskContainer.getTask();
return InstantAppResolver.newInstance(container.asContext()).isInstantApp(
t.getTopComponent().getPackageName(), t.getKey().userId)
@@ -457,7 +447,7 @@
TaskShortcutFactory WELLBEING = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
SystemShortcut<ActivityContext> wellbeingShortcut =
WellbeingModel.SHORTCUT_FACTORY.getShortcut(container,
taskContainer.getItemInfo(), taskContainer.getTaskView());
@@ -468,7 +458,7 @@
TaskShortcutFactory SCREENSHOT = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
boolean isTablet = container.getDeviceProfile().isTablet;
boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
// Extra conditions if it's not grid-only overview
@@ -498,7 +488,7 @@
TaskShortcutFactory MODAL = new TaskShortcutFactory() {
@Override
public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
- TaskIdAttributeContainer taskContainer) {
+ TaskContainer taskContainer) {
boolean isTablet = container.getDeviceProfile().isTablet;
boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
// Extra conditions if it's not grid-only overview
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index d89d399..d2560e6 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -121,7 +121,7 @@
for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
TaskView taskView = recentsView.getTaskViewAt(i);
if (recentsView.isTaskViewVisible(taskView)) {
- Task.TaskKey key = taskView.getTask().key;
+ Task.TaskKey key = taskView.getFirstTask().key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
return taskView;
}
@@ -334,7 +334,7 @@
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnails();
+ TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews();
// In case simulator copies and thumbnail size do no match, ensure we get the lesser.
// This ensures we do not create arrays with empty elements or attempt to references
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 2e76356..1bf129c 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -46,12 +46,10 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.MultiPropertyFactory;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
/**
* State controller for fallback recents activity
@@ -98,8 +96,8 @@
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
- setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
- MultiPropertyFactory.MULTI_PROPERTY_VALUE, overviewButtonAlpha, LINEAR);
+ setter.setFloat(mActivity.getActionsView().getVisibilityAlphaSetter(),
+ OverviewActionsView.FLOAT_SETTER, overviewButtonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index e3e14ae..3cae4dc 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -392,6 +392,20 @@
case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
break;
+ case LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_LOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_LOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
+ break;
+ case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
+ break;
default:
break;
}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
new file mode 100644
index 0000000..28212cf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.recents.viewmodel
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+// This is far from complete but serves the purpose of enabling refactoring in other areas
+class RecentsViewData {
+ val fullscreenProgress = MutableStateFlow(1f)
+
+ // This is typically a View concern but it is used to invalidate rendering in other Views
+ val scale = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index d51069f..b466f3f 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -17,22 +17,44 @@
package com.android.quickstep.task.thumbnail
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Canvas
+import android.graphics.Outline
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.*
+import android.view.ViewOutlineProvider
+import com.android.launcher3.Utilities
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.util.TaskCornerRadius
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import com.android.systemui.shared.system.QuickStepContract
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
class TaskThumbnailView : View {
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
// to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
- val viewModel = TaskThumbnailViewModel()
+ // This is using a lazy for now because the dependencies cannot be obtained without DI.
+ val viewModel by lazy {
+ TaskThumbnailViewModel(
+ RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
+ .getOverviewPanel<RecentsView<*, *>>()
+ .mRecentsViewData,
+ (parent as TaskView).mTaskViewData
+ )
+ }
private var uiState: TaskThumbnailUiState = Uninitialized
+ private var inheritedScale: Float = 1f
+
+ private var cornerRadius: Float = TaskCornerRadius.get(context)
+ private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
@@ -51,6 +73,27 @@
invalidate()
}
}
+ MainScope().launch { viewModel.recentsFullscreenProgress.collect { invalidateOutline() } }
+ MainScope().launch {
+ viewModel.inheritedScale.collect { viewModelInheritedScale ->
+ inheritedScale = viewModelInheritedScale
+ invalidateOutline()
+ }
+ }
+
+ clipToOutline = true
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.measuredWidth,
+ view.measuredHeight,
+ getCurrentCornerRadius()
+ )
+ }
+ }
}
override fun onDraw(canvas: Canvas) {
@@ -60,19 +103,25 @@
}
}
- private fun drawTransparentUiState(canvas: Canvas) {
- canvas.drawRoundRect(
- 0f,
- 0f,
- measuredWidth.toFloat(),
- measuredHeight.toFloat(),
- // TODO(b/334826840) add rounded corners
- 0f,
- 0f,
- CLEAR_PAINT
- )
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+
+ cornerRadius = TaskCornerRadius.get(context)
+ fullscreenCornerRadius = QuickStepContract.getWindowCornerRadius(context)
+ invalidateOutline()
}
+ private fun drawTransparentUiState(canvas: Canvas) {
+ canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT)
+ }
+
+ private fun getCurrentCornerRadius() =
+ Utilities.mapRange(
+ viewModel.recentsFullscreenProgress.value,
+ cornerRadius,
+ fullscreenCornerRadius
+ ) / inheritedScale
+
companion object {
private val CLEAR_PAINT =
Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
index 9925873..71bc865 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
@@ -16,20 +16,32 @@
package com.android.quickstep.task.thumbnail
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskViewData
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
-class TaskThumbnailViewModel {
- private val _uiState: MutableStateFlow<TaskThumbnailUiState> =
- MutableStateFlow(TaskThumbnailUiState.Uninitialized)
- val uiState: StateFlow<TaskThumbnailUiState> = _uiState
+class TaskThumbnailViewModel(recentsViewData: RecentsViewData, taskViewData: TaskViewData) {
+ private val task = MutableStateFlow<TaskThumbnail?>(null)
+
+ val recentsFullscreenProgress = recentsViewData.fullscreenProgress
+ val inheritedScale =
+ combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
+ recentsScale * taskScale
+ }
+ val uiState =
+ task.map { taskVal ->
+ when {
+ taskVal == null -> Uninitialized
+ taskVal.isRunning -> LiveTile
+ else -> Uninitialized
+ }
+ }
fun bind(task: TaskThumbnail) {
- _uiState.value =
- if (task.isRunning) {
- TaskThumbnailUiState.LiveTile
- } else {
- TaskThumbnailUiState.Uninitialized
- }
+ this.task.value = task
}
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
new file mode 100644
index 0000000..a8b5112
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.task.viewmodel
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class TaskViewData {
+ // This is typically a View concern but it is used to invalidate rendering in other Views
+ val scale = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index a82031a..e4e2eb2 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -22,6 +22,7 @@
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SUPPORTS_MULTI_INSTANCE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -32,34 +33,38 @@
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
@@ -101,6 +106,55 @@
}
/**
+ * Returns whether the specified GroupedTaskView can be saved as an app pair.
+ */
+ public boolean canSaveAppPair(TaskView taskView) {
+ if (mContext == null) {
+ // Can ignore as the activity is already destroyed
+ return false;
+ }
+
+ // Disallow saving app pairs if:
+ // - app pairs feature is not enabled
+ // - the task in question is a single task
+ // - at least one app in app pair is unpinnable
+ // - the task is not a GroupedTaskView
+ // - both tasks in the GroupedTaskView are from the same app and the app does not
+ // support multi-instance
+ boolean hasUnpinnableApp = taskView.getTaskContainers().stream()
+ .anyMatch(att -> att != null && att.getItemInfo() != null
+ && ((att.getItemInfo().runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
+ if (!FeatureFlags.enableAppPairs()
+ || !taskView.containsMultipleTasks()
+ || hasUnpinnableApp
+ || !(taskView instanceof GroupedTaskView)) {
+ return false;
+ }
+
+ GroupedTaskView gtv = (GroupedTaskView) taskView;
+ List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
+ ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
+ containers.get(0).getTask().key);
+ ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
+ containers.get(1).getTask().key);
+ AppInfo app1 = resolveAppInfoByComponent(taskKey1);
+ AppInfo app2 = resolveAppInfoByComponent(taskKey2);
+
+ if (app1 == null || app2 == null) {
+ // Disallow saving app pairs for apps that don't have a front-door in Launcher
+ return false;
+ }
+
+ if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
+ if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Creates a new app pair ItemInfo and adds it to the workspace.
* <br>
* We create WorkspaceItemInfos to save onto the app pair in the following way:
@@ -116,34 +170,26 @@
*/
public void saveAppPair(GroupedTaskView gtv) {
InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
- TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
- WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
- WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo();
- WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey());
- WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey());
+ List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
+ WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
+ WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
+ WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
+ WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
- // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default
- // intent with one from PackageManager.
- if (app1 == null) {
- Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title
- + " failed. Falling back to the WorkspaceItemInfo from Recents.");
- app1 = convertRecentsItemToAppItem(recentsInfo1);
+ if (app1 == null || app2 == null) {
+ // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+ // not create the app pair if the workspace items can't be resolved
+ Log.w(TAG, "Failed to save app pair due to invalid apps ("
+ + "app1=" + recentsInfo1.getComponentKey().componentName
+ + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
+ return;
}
- if (app2 == null) {
- Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title
- + " failed. Falling back to the WorkspaceItemInfo from Recents.");
- app2 = convertRecentsItemToAppItem(recentsInfo2);
- }
-
- // WorkspaceItemProcessor won't process these new ItemInfos until the next launcher restart,
- // so update some flags now.
- updateWorkspaceItemFlags(app1);
- updateWorkspaceItemFlags(app2);
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (!isPersistentSnapPosition(snapPosition)) {
- // if we received an illegal snap position, log an error and do not create the app pair.
- Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition);
+ // If we received an illegal snap position, log an error and do not create the app pair
+ Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition "
+ + snapPosition);
return;
}
@@ -229,67 +275,38 @@
}
/**
+ * Returns an AppInfo associated with the app for the given ComponentKey, or null if no such
+ * package exists in the AllAppsStore.
+ */
+ @Nullable
+ private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
+ AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
+ .getAppsView().getAppsStore();
+
+ // First look up the app info in order of:
+ // - The exact activity for the recent task
+ // - The first(?) loaded activity from the package
+ AppInfo appInfo = appsStore.getApp(key);
+ if (appInfo == null) {
+ appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
+ }
+ return appInfo;
+ }
+
+ /**
* Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the
* ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package
* instead. If that lookup fails, returns null.
*/
@Nullable
- private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) {
- if (key == null) {
+ private WorkspaceItemInfo resolveAppPairWorkspaceInfo(
+ @NonNull WorkspaceItemInfo recentTaskInfo) {
+ // ComponentKey should never be null (see TaskView#getItemInfo)
+ AppInfo appInfo = resolveAppInfoByComponent(recentTaskInfo.getComponentKey());
+ if (appInfo == null) {
return null;
}
-
- AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
- .getAppsView().getAppsStore();
-
- // Lookup by ComponentKey
- AppInfo appInfo = appsStore.getApp(key);
- if (appInfo == null) {
- // Lookup by package
- appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
- }
-
- return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null;
- }
-
- /**
- * Updates flags for newly created WorkspaceItemInfos.
- */
- private void updateWorkspaceItemFlags(WorkspaceItemInfo wii) {
- PackageManager pm = mContext.getPackageManager();
- ActivityInfo ai = null;
- try {
- ai = pm.getActivityInfo(wii.getTargetComponent(), 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "PackageManager lookup failed.");
- }
-
- if (ai != null) {
- wii.setNonResizeable(ai.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE);
- }
- }
-
- /**
- * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new
- * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION.
- */
- private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) {
- if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) {
- Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received "
- + recentsItem.itemType);
- }
-
- WorkspaceItemInfo launchableItem = recentsItem.clone();
- PackageManager p = mContext.getPackageManager();
- Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage());
- Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n"
- + "Intent from PackageManager: " + launchIntent);
- if (launchIntent != null) {
- // If lookup from PackageManager fails, just use the existing intent
- launchableItem.intent = launchIntent;
- }
- launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- return launchableItem;
+ return appInfo.makeWorkspaceItem(mContext);
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index 42db65f..7acb28d 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -33,8 +33,8 @@
public AssistStateManager() {}
- /** Whether search is available. */
- public boolean isSearchAvailable() {
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsAllEntrypointsEnabled() {
return false;
}
@@ -43,14 +43,9 @@
return false;
}
- /** Whether CsHelper CtS invocation path is available. */
- public Optional<Boolean> isCsHelperAvailable() {
- return Optional.empty();
- }
-
- /** Whether VIS CtS invocation path is available. */
- public Optional<Boolean> isVisAvailable() {
- return Optional.empty();
+ /** Whether ContextualSearchService invocation path is available. */
+ public boolean isContextualSearchServiceAvailable() {
+ return false;
}
/** Get the Launcher overridden long press nav handle duration to trigger Assistant. */
@@ -90,11 +85,6 @@
return Optional.empty();
}
- /** Return {@code true} if the Settings toggle is enabled. */
- public boolean isSettingsAllEntrypointsEnabled() {
- return false;
- }
-
/** Dump states. */
public void dump(String prefix, PrintWriter writer) {}
diff --git a/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
index 544c64d..d36dc7e 100644
--- a/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
+++ b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
@@ -48,12 +48,14 @@
PropReader(
object : PropProvider {
override fun <T : Any> get(key: String, fallback: T): T {
- if (fallback is Int)
+ if (fallback is Int) {
+ allKeys.add(key)
return DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, fallback) as T
- else if (fallback is Boolean)
+ } else if (fallback is Boolean) {
+ allKeys.add(key)
return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, fallback)
as T
- else return fallback
+ } else return fallback
}
}
)
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index ee2c2e1..40ea70f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -69,7 +69,7 @@
import com.android.quickstep.views.SplitInstructionsView
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
import com.android.quickstep.views.TaskViewIcon
import com.android.wm.shell.shared.TransitionUtil
import java.util.Optional
@@ -114,7 +114,7 @@
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
val taskView = taskViewSupplier.get()
- for (container: TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+ for (container: TaskContainer in taskView.taskContainers) {
if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
val drawable = getDrawable(container.iconView, splitSelectSource)
return SplitAnimInitProps(
@@ -134,15 +134,17 @@
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
- val drawable = getDrawable(taskView.iconView, splitSelectSource)
- return SplitAnimInitProps(
- taskView.thumbnail,
- taskView.thumbnail.thumbnail,
- drawable!!,
- fadeWithThumbnail = true,
- isStagedTask = true,
- taskView.iconView.asView()
- )
+ taskView.taskContainers.first().let {
+ val drawable = getDrawable(it.iconView, splitSelectSource)
+ return SplitAnimInitProps(
+ it.thumbnailView,
+ it.thumbnailView.thumbnail,
+ drawable!!,
+ fadeWithThumbnail = true,
+ isStagedTask = true,
+ iconView = it.iconView.asView()
+ )
+ }
}
}
@@ -173,7 +175,7 @@
* (opposite of that representing [taskIdAttributeContainer])
*/
fun addInitialSplitFromPair(
- taskIdAttributeContainer: TaskIdAttributeContainer,
+ taskIdAttributeContainer: TaskContainer,
builder: PendingAnimation,
deviceProfile: DeviceProfile,
taskViewWidth: Int,
@@ -561,8 +563,13 @@
// Launch split app pair animation
composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
} else {
- composeFullscreenIconSplitLaunchAnimator(launchingIconView, info, t,
- finishCallback, appPairLaunchingAppIndex)
+ composeFullscreenIconSplitLaunchAnimator(
+ launchingIconView,
+ info,
+ t,
+ finishCallback,
+ appPairLaunchingAppIndex
+ )
}
} else {
// Fallback case: simple fade-in animation
@@ -629,18 +636,22 @@
/**
* @return -1 if [transitionInfo] contains both apps of the app pair to be animated, otherwise
- * the integer index corresponding to [launchingIconView]'s contents for the single app
- * to be animated
+ * the integer index corresponding to [launchingIconView]'s contents for the single app to be
+ * animated
*/
- fun hasChangesForBothAppPairs(launchingIconView: AppPairIcon,
- transitionInfo: TransitionInfo) : Int {
+ fun hasChangesForBothAppPairs(
+ launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo
+ ): Int {
val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
var launchFullscreenAppIndex = -1
for (change in transitionInfo.changes) {
val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
- if (TransitionUtil.isOpeningType(change.mode) &&
- taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (
+ TransitionUtil.isOpeningType(change.mode) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN
+ ) {
val baseIntent = taskInfo.baseIntent.component?.packageName
if (baseIntent == intent1) {
if (launchFullscreenAppIndex > -1) {
@@ -695,8 +706,12 @@
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
if (launchingIconView.context is TaskbarActivityContext) {
- composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
- WINDOWING_MODE_MULTI_WINDOW)
+ composeScaleUpLaunchAnimation(
+ transitionInfo,
+ t,
+ finishCallback,
+ WINDOWING_MODE_MULTI_WINDOW
+ )
return
}
@@ -767,28 +782,32 @@
floatingView.bringToFront()
launchAnimation.play(
- getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
- rootCandidate))
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ )
launchAnimation.start()
}
/**
- * Similar to [composeIconSplitLaunchAnimator], but instructs [FloatingAppPairView] to animate
- * a single fullscreen icon + background instead of for a pair
+ * Similar to [composeIconSplitLaunchAnimator], but instructs [FloatingAppPairView] to animate a
+ * single fullscreen icon + background instead of for a pair
*/
@VisibleForTesting
fun composeFullscreenIconSplitLaunchAnimator(
- launchingIconView: AppPairIcon,
- transitionInfo: TransitionInfo,
- t: Transaction,
- finishCallback: Runnable,
- launchFullscreenIndex: Int
+ launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ launchFullscreenIndex: Int
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
if (launchingIconView.context is TaskbarActivityContext) {
- composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
- WINDOWING_MODE_FULLSCREEN)
+ composeScaleUpLaunchAnimation(
+ transitionInfo,
+ t,
+ finishCallback,
+ WINDOWING_MODE_FULLSCREEN
+ )
return
}
@@ -799,16 +818,18 @@
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- val appInfo = launchingIconView.info
- .getContents()[launchFullscreenIndex] as WorkspaceItemInfo
+ val appInfo =
+ launchingIconView.info.getContents()[launchFullscreenIndex] as WorkspaceItemInfo
val intentToLaunch = appInfo.intent.component?.packageName
var rootCandidate: Change? = null
for (change in transitionInfo.changes) {
val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
val baseIntent = taskInfo.baseIntent.component?.packageName
- if (TransitionUtil.isOpeningType(change.mode) &&
+ if (
+ TransitionUtil.isOpeningType(change.mode) &&
taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN &&
- baseIntent == intentToLaunch) {
+ baseIntent == intentToLaunch
+ ) {
rootCandidate = change
}
}
@@ -832,26 +853,28 @@
appIcon.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
val floatingView =
- FloatingAppPairView.getFloatingAppPairView(
- launcher,
- drawableArea,
- appIcon,
- null /*appIcon2*/,
- 0 /*dividerPos*/
- )
+ FloatingAppPairView.getFloatingAppPairView(
+ launcher,
+ drawableArea,
+ appIcon,
+ null /*appIcon2*/,
+ 0 /*dividerPos*/
+ )
floatingView.bringToFront()
launchAnimation.play(
- getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
- rootCandidate))
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ )
launchAnimation.start()
}
- private fun getIconLaunchValueAnimator(t: Transaction,
- dp: com.android.launcher3.DeviceProfile,
- finishCallback: Runnable,
- launcher: QuickstepLauncher,
- floatingView: FloatingAppPairView,
- rootCandidate: Change) : ValueAnimator {
+ private fun getIconLaunchValueAnimator(
+ t: Transaction,
+ dp: com.android.launcher3.DeviceProfile,
+ finishCallback: Runnable,
+ launcher: QuickstepLauncher,
+ floatingView: FloatingAppPairView,
+ rootCandidate: Change
+ ): ValueAnimator {
val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
progressUpdater.setDuration(timings.getDuration().toLong())
@@ -860,12 +883,12 @@
// Shell animation: the apps are revealed toward end of the launch animation
progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
val progress =
- Interpolators.clampToProgress(
- Interpolators.LINEAR,
- valueAnimator.animatedFraction,
- timings.appRevealStartOffset,
- timings.appRevealEndOffset
- )
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ valueAnimator.animatedFraction,
+ timings.appRevealStartOffset,
+ timings.appRevealEndOffset
+ )
// Set the alpha of the shell layer (2 apps + divider)
t.setAlpha(rootCandidate.leash, progress)
@@ -873,65 +896,65 @@
}
progressUpdater.addUpdateListener(
- object : MultiValueUpdateListener() {
- var mDx =
- FloatProp(
- floatingView.startingPosition.left,
- dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
- Interpolators.clampToProgress(
- timings.getStagedRectXInterpolator(),
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mDy =
- FloatProp(
- floatingView.startingPosition.top,
- dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mScaleX =
- FloatProp(
- 1f /* start */,
- dp.widthPx / floatingView.startingPosition.width(),
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mScaleY =
- FloatProp(
- 1f /* start */,
- dp.heightPx / floatingView.startingPosition.height(),
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
+ object : MultiValueUpdateListener() {
+ var mDx =
+ FloatProp(
+ floatingView.startingPosition.left,
+ dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
+ Interpolators.clampToProgress(
+ timings.getStagedRectXInterpolator(),
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mDy =
+ FloatProp(
+ floatingView.startingPosition.top,
+ dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mScaleX =
+ FloatProp(
+ 1f /* start */,
+ dp.widthPx / floatingView.startingPosition.width(),
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mScaleY =
+ FloatProp(
+ 1f /* start */,
+ dp.heightPx / floatingView.startingPosition.height(),
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
- override fun onUpdate(percent: Float, initOnly: Boolean) {
- floatingView.progress = percent
- floatingView.x = mDx.value
- floatingView.y = mDy.value
- floatingView.scaleX = mScaleX.value
- floatingView.scaleY = mScaleY.value
- floatingView.invalidate()
- }
+ override fun onUpdate(percent: Float, initOnly: Boolean) {
+ floatingView.progress = percent
+ floatingView.x = mDx.value
+ floatingView.y = mDy.value
+ floatingView.scaleX = mScaleX.value
+ floatingView.scaleY = mScaleY.value
+ floatingView.invalidate()
}
+ }
)
progressUpdater.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- safeRemoveViewFromDragLayer(launcher, floatingView)
- finishCallback.run()
- }
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ safeRemoveViewFromDragLayer(launcher, floatingView)
+ finishCallback.run()
}
+ }
)
return progressUpdater
@@ -939,9 +962,8 @@
/**
* This is a scale-up-and-fade-in animation (34% to 100%) for launching an app in Overview when
- * there is no visible associated tile to expand from.
- * [windowingMode] helps determine whether we are looking for a split or a single fullscreen
- * [Change]
+ * there is no visible associated tile to expand from. [windowingMode] helps determine whether
+ * we are looking for a split or a single fullscreen [Change]
*/
@VisibleForTesting
fun composeScaleUpLaunchAnimation(
@@ -1095,12 +1117,12 @@
animator.setDuration(QuickstepTransitionManager.SPLIT_LAUNCH_DURATION.toLong())
animator.addUpdateListener { valueAnimator: ValueAnimator ->
val progress =
- Interpolators.clampToProgress(
- Interpolators.LINEAR,
- valueAnimator.animatedFraction,
- 0.8f,
- 1f
- )
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ valueAnimator.animatedFraction,
+ 0.8f,
+ 1f
+ )
for (leash in openingTargets) {
animTransaction.setAlpha(leash, progress)
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 06edb14..8258ab8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -38,36 +38,40 @@
import java.io.PrintWriter
/**
- * Holds/transforms/signs/seals/delivers information for the transient state of the user
- * selecting a first app to start split with and then choosing a second app.
- * This class DOES NOT associate itself with drag-and-drop split screen starts because they come
- * from the bad part of town.
+ * Holds/transforms/signs/seals/delivers information for the transient state of the user selecting a
+ * first app to start split with and then choosing a second app. This class DOES NOT associate
+ * itself with drag-and-drop split screen starts because they come from the bad part of town.
*
* After setting the correct fields for initial/second.* variables, this converts them into the
- * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
- * data back via [getSplitLaunchData]. Note: there should be only one "initial" field and one
- * "second" field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are
- * always passed in together as a set, and are converted to a single [PendingIntent] or
+ * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary data
+ * back via [getSplitLaunchData]. Note: there should be only one "initial" field and one "second"
+ * field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are always passed
+ * in together as a set, and are converted to a single [PendingIntent] or
* [ShortcutInfo]+[PendingIntent] before launch.)
*
* [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
* state
*/
-class SplitSelectDataHolder(
- var context: Context?
-) {
+class SplitSelectDataHolder(var context: Context?) {
val TAG = SplitSelectDataHolder::class.simpleName
/**
- * Order of the constant indicates the order of which task/app was selected.
- * Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
+ * Order of the constant indicates the order of which task/app was selected. Ex.
+ * SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
* SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task
*/
companion object {
- @IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT,
- SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK,
- SPLIT_SINGLE_TASK_FULLSCREEN, SPLIT_SINGLE_INTENT_FULLSCREEN,
- SPLIT_SINGLE_SHORTCUT_FULLSCREEN)
+ @IntDef(
+ SPLIT_TASK_TASK,
+ SPLIT_TASK_PENDINGINTENT,
+ SPLIT_TASK_SHORTCUT,
+ SPLIT_PENDINGINTENT_TASK,
+ SPLIT_PENDINGINTENT_PENDINGINTENT,
+ SPLIT_SHORTCUT_TASK,
+ SPLIT_SINGLE_TASK_FULLSCREEN,
+ SPLIT_SINGLE_INTENT_FULLSCREEN,
+ SPLIT_SINGLE_SHORTCUT_FULLSCREEN
+ )
@Retention(AnnotationRetention.SOURCE)
annotation class SplitLaunchType
@@ -84,8 +88,7 @@
const val SPLIT_SINGLE_SHORTCUT_FULLSCREEN = 8
}
- @StagePosition
- private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ @StagePosition private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
private var itemInfo: ItemInfo? = null
private var secondItemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
@@ -108,12 +111,16 @@
/**
* @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
- * then @param intent will be used to launch the initial task
+ * then @param intent will be used to launch the initial task
* @param intent will be ignored if @param alreadyRunningTask is set
*/
- fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int,
- itemInfo: ItemInfo?, splitEvent: EventEnum?,
- alreadyRunningTask: Int) {
+ fun setInitialTaskSelect(
+ intent: Intent?,
+ @StagePosition stagePosition: Int,
+ itemInfo: ItemInfo?,
+ splitEvent: EventEnum?,
+ alreadyRunningTask: Int
+ ) {
if (alreadyRunningTask != INVALID_TASK_ID) {
initialTaskId = alreadyRunningTask
} else {
@@ -127,15 +134,21 @@
* To be called after first task selected from using a split shortcut from the fullscreen
* running app.
*/
- fun setInitialTaskSelect(info: RunningTaskInfo,
- @StagePosition stagePosition: Int, itemInfo: ItemInfo?,
- splitEvent: EventEnum?) {
+ fun setInitialTaskSelect(
+ info: RunningTaskInfo,
+ @StagePosition stagePosition: Int,
+ itemInfo: ItemInfo?,
+ splitEvent: EventEnum?
+ ) {
initialTaskId = info.taskId
setInitialData(stagePosition, splitEvent, itemInfo)
}
- private fun setInitialData(@StagePosition stagePosition: Int,
- event: EventEnum?, item: ItemInfo?) {
+ private fun setInitialData(
+ @StagePosition stagePosition: Int,
+ event: EventEnum?,
+ item: ItemInfo?
+ ) {
itemInfo = item
initialStagePosition = stagePosition
splitEvent = event
@@ -143,6 +156,7 @@
/**
* To be called as soon as user selects the second task (even if animations aren't complete)
+ *
* @param taskId The second task that will be launched.
*/
fun setSecondTask(taskId: Int, itemInfo: ItemInfo) {
@@ -152,6 +166,7 @@
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
+ *
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
@@ -162,8 +177,9 @@
}
/**
- * To be called as soon as user selects the second app (even if animations aren't complete)
- * Sets [secondUser] from that of the pendingIntent
+ * To be called as soon as user selects the second app (even if animations aren't complete) Sets
+ * [secondUser] from that of the pendingIntent
+ *
* @param pendingIntent The second PendingIntent that will be launched.
*/
fun setSecondTask(pendingIntent: PendingIntent, itemInfo: ItemInfo) {
@@ -173,9 +189,9 @@
}
/**
- * Similar to [setSecondTask] except this is to be called for widgets which can pass through
- * an extra intent from their RemoteResponse.
- * See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
+ * Similar to [setSecondTask] except this is to be called for widgets which can pass through an
+ * extra intent from their RemoteResponse. See
+ * [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
*/
fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?, itemInfo: ItemInfo) {
setSecondTask(pendingIntent, itemInfo)
@@ -184,8 +200,7 @@
private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
val intentPackage = intent?.getPackage() ?: return null
- val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
- ?: return null
+ val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) ?: return null
try {
val context: Context =
if (user != null) {
@@ -200,9 +215,7 @@
return null
}
- /**
- * Converts intents to pendingIntents, associating the [user] with the intent if provided
- */
+ /** Converts intents to pendingIntents, associating the [user] with the intent if provided */
private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? {
if (intent != initialIntent && intent != secondIntent) {
throw IllegalStateException("Invalid intent to convert to PendingIntent")
@@ -211,12 +224,21 @@
return if (intent == null) {
null
} else if (user != null) {
- PendingIntent.getActivityAsUser(context, 0, intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
- null /* options */, user)
+ PendingIntent.getActivityAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
+ null /* options */,
+ user
+ )
} else {
- PendingIntent.getActivity(context, 0, intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
+ PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
+ )
}
}
@@ -224,7 +246,7 @@
* @return [SplitLaunchData] with the necessary fields populated as determined by
* [SplitLaunchData.splitLaunchType]. This is to be used for launching splitscreen
*/
- fun getSplitLaunchData() : SplitLaunchData {
+ fun getSplitLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getSplitLaunchType()
@@ -241,7 +263,7 @@
* [SplitLaunchData.splitLaunchType]. This is to be used for launching an initially selected
* split task in fullscreen
*/
- fun getFullscreenLaunchData() : SplitLaunchData {
+ fun getFullscreenLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getFullscreenLaunchType()
@@ -249,21 +271,22 @@
return generateSplitLaunchData(splitLaunchType)
}
- private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int) : SplitLaunchData {
+ private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int): SplitLaunchData {
return SplitLaunchData(
- splitLaunchType,
- initialTaskId,
- secondTaskId,
- initialPendingIntent,
- secondPendingIntent,
- widgetSecondIntent,
- initialUser?.identifier ?: -1,
- secondUser?.identifier ?: -1,
- initialShortcut,
- secondShortcut,
- itemInfo,
- splitEvent,
- initialStagePosition)
+ splitLaunchType,
+ initialTaskId,
+ secondTaskId,
+ initialPendingIntent,
+ secondPendingIntent,
+ widgetSecondIntent,
+ initialUser?.identifier ?: -1,
+ secondUser?.identifier ?: -1,
+ initialShortcut,
+ secondShortcut,
+ itemInfo,
+ splitEvent,
+ initialStagePosition
+ )
}
/**
@@ -273,8 +296,7 @@
* Note that both [initialIntent] and [secondIntent] will be nullified on method return
*
* One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
- * convert [secondIntent].
- * This also leaves [widgetSecondIntent] untouched.
+ * convert [secondIntent]. This also leaves [widgetSecondIntent] untouched.
*/
private fun convertIntentsToFinalTypes() {
initialShortcut = getShortcutInfo(initialIntent, initialUser)
@@ -297,8 +319,8 @@
}
/**
- * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
- * Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
+ * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents Intents
+ * need to be converted in [convertIntentsToFinalTypes] prior to calling this method
*/
@VisibleForTesting
@SplitLaunchType
@@ -354,41 +376,40 @@
}
data class SplitLaunchData(
- @SplitLaunchType
- val splitLaunchType: Int,
- var initialTaskId: Int = INVALID_TASK_ID,
- var secondTaskId: Int = INVALID_TASK_ID,
- var initialPendingIntent: PendingIntent? = null,
- var secondPendingIntent: PendingIntent? = null,
- var widgetSecondIntent: Intent? = null,
- var initialUserId: Int = -1,
- var secondUserId: Int = -1,
- var initialShortcut: ShortcutInfo? = null,
- var secondShortcut: ShortcutInfo? = null,
- var itemInfo: ItemInfo? = null,
- var splitEvent: EventEnum? = null,
- val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ @SplitLaunchType val splitLaunchType: Int,
+ var initialTaskId: Int = INVALID_TASK_ID,
+ var secondTaskId: Int = INVALID_TASK_ID,
+ var initialPendingIntent: PendingIntent? = null,
+ var secondPendingIntent: PendingIntent? = null,
+ var widgetSecondIntent: Intent? = null,
+ var initialUserId: Int = -1,
+ var secondUserId: Int = -1,
+ var initialShortcut: ShortcutInfo? = null,
+ var secondShortcut: ShortcutInfo? = null,
+ var itemInfo: ItemInfo? = null,
+ var splitEvent: EventEnum? = null,
+ val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
)
/**
- * @return `true` if first task has been selected and waiting for the second task to be
- * chosen
+ * @return `true` if first task has been selected and waiting for the second task to be chosen
*/
fun isSplitSelectActive(): Boolean {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet()
}
/**
- * @return `true` if the first and second task have been chosen and split is waiting to
- * be launched
+ * @return `true` if the first and second task have been chosen and split is waiting to be
+ * launched
*/
fun isBothSplitAppsConfirmed(): Boolean {
return isInitialTaskIntentSet() && isSecondTaskIntentSet()
}
private fun isInitialTaskIntentSet(): Boolean {
- return initialTaskId != INVALID_TASK_ID || initialIntent != null ||
- initialPendingIntent != null
+ return initialTaskId != INVALID_TASK_ID ||
+ initialIntent != null ||
+ initialPendingIntent != null
}
fun getInitialTaskId(): Int {
@@ -416,8 +437,9 @@
}
private fun isSecondTaskIntentSet(): Boolean {
- return secondTaskId != INVALID_TASK_ID || secondIntent != null
- || secondPendingIntent != null
+ return secondTaskId != INVALID_TASK_ID ||
+ secondIntent != null ||
+ secondPendingIntent != null
}
fun resetState() {
@@ -437,7 +459,7 @@
}
fun dump(prefix: String, writer: PrintWriter) {
- writer.println("$prefix ${javaClass.simpleName}")
+ writer.println("$prefix SplitSelectDataHolder")
writer.println("$prefix\tinitialStagePosition= $initialStagePosition")
writer.println("$prefix\tinitialTaskId= $initialTaskId")
writer.println("$prefix\tsecondTaskId= $secondTaskId")
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index f823aff..99f10a7 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -46,7 +46,7 @@
* when swiping up (in gesture navigation mode).
*/
public class SwipePipToHomeAnimator extends RectFSpringAnim {
- private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
+ private static final String TAG = "SwipePipToHomeAnimator";
private static final float END_PROGRESS = 1.0f;
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
index 21c9e09..69137cc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
@@ -37,7 +37,7 @@
* @param <V> Type of object stored in the cache
*/
public class TaskKeyByLastActiveTimeCache<V> implements TaskKeyCache<V> {
- private static final String TAG = TaskKeyByLastActiveTimeCache.class.getSimpleName();
+ private static final String TAG = "TaskKeyByLastActiveTimeCache";
private final AtomicInteger mMaxSize;
private final Map<Integer, Entry<V>> mMap;
// To sort task id by last active time
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 4915b62..1bedad4 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -16,7 +16,7 @@
package com.android.quickstep.views;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
@@ -31,7 +31,6 @@
import android.graphics.drawable.shapes.RoundRectShape;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.SparseArray;
import android.view.View;
import android.widget.FrameLayout;
@@ -43,6 +42,7 @@
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.ViewPool;
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskThumbnailCache;
@@ -66,18 +66,10 @@
// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
public class DesktopTaskView extends TaskView {
- private static final String TAG = DesktopTaskView.class.getSimpleName();
+ private static final String TAG = "DesktopTaskView";
private static final boolean DEBUG = false;
- @NonNull
- private List<Task> mTasks = new ArrayList<>();
-
- private final ArrayList<TaskThumbnailViewDeprecated> mSnapshotViews = new ArrayList<>();
-
- /** Maps {@code taskIds} to corresponding {@link TaskThumbnailViewDeprecated}s */
- private final SparseArray<TaskThumbnailViewDeprecated> mSnapshotViewMap = new SparseArray<>();
-
private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
private final TaskView.FullscreenDrawParams mSnapshotDrawParams;
@@ -88,6 +80,8 @@
private final PointF mTempPointF = new PointF();
+ private final ViewPool<TaskThumbnailViewDeprecated> mTaskthumbnailViewPool;
+
public DesktopTaskView(Context context) {
this(context, null);
}
@@ -110,6 +104,10 @@
return QuickStepContract.getWindowCornerRadius(context);
}
};
+ // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
+ mTaskthumbnailViewPool = new ViewPool<>(context, this, R.layout.task_thumbnail,
+ /* maxSize= */10, /* initialSize= */ 0);
+ mTaskContainers = new ArrayList<>();
}
@Override
@@ -146,8 +144,7 @@
mContainer.getDragLayer().getDescendantRectRelativeToSelf(mBackgroundView, bounds);
} else {
bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(),
- mBackgroundView.getRight(),
- mBackgroundView.getBottom());
+ mBackgroundView.getRight(), mBackgroundView.getBottom());
}
return Unit.INSTANCE;
}
@@ -171,83 +168,36 @@
}
cancelPendingLoadTasks();
- mTasks = new ArrayList<>(tasks);
- mSnapshotViewMap.clear();
-
- // Ensure there are equal number of snapshot views and tasks.
- // More tasks than views, add views. More views than tasks, remove views.
- // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
- if (mSnapshotViews.size() > mTasks.size()) {
- int diff = mSnapshotViews.size() - mTasks.size();
- for (int i = 0; i < diff; i++) {
- TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.remove(0);
- removeView(snapshotView);
- }
- } else if (mSnapshotViews.size() < mTasks.size()) {
- int diff = mTasks.size() - mSnapshotViews.size();
- for (int i = 0; i < diff; i++) {
- TaskThumbnailViewDeprecated snapshotView =
- new TaskThumbnailViewDeprecated(getContext());
- mSnapshotViews.add(snapshotView);
- // Add snapshots from to position after the initial child views.
- addView(snapshotView, mChildCountAtInflation,
+ ((ArrayList<TaskContainer>) mTaskContainers).ensureCapacity(tasks.size());
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.get(i);
+ TaskThumbnailViewDeprecated thumbnailView;
+ if (i >= mTaskContainers.size()) {
+ thumbnailView = mTaskthumbnailViewPool.getView();
+ // Add thumbnailView from to position after the initial child views.
+ addView(thumbnailView, mChildCountAtInflation,
new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ } else {
+ thumbnailView = mTaskContainers.get(i).getThumbnailView();
+ }
+ thumbnailView.bind(task);
+ TaskContainer taskContainer = new TaskContainer(task, thumbnailView, mIconView,
+ STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null);
+ if (i >= mTaskContainers.size()) {
+ mTaskContainers.add(taskContainer);
+ } else {
+ mTaskContainers.set(i, taskContainer);
}
}
-
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
- TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.get(i);
- snapshotView.bind(task);
- mSnapshotViewMap.put(task.key.id, snapshotView);
+ while (mTaskContainers.size() > tasks.size()) {
+ TaskContainer taskContainer = mTaskContainers.remove(mTaskContainers.size() - 1);
+ removeView(taskContainer.getThumbnailView());
+ mTaskthumbnailViewPool.recycle(taskContainer.getThumbnailView());
}
- updateTaskIdContainer();
- updateTaskIdAttributeContainer();
-
setOrientationState(orientedState);
}
- private void updateTaskIdContainer() {
- mTaskIdContainer = new int[mTasks.size()];
- for (int i = 0; i < mTasks.size(); i++) {
- mTaskIdContainer[i] = mTasks.get(i).key.id;
- }
- }
-
- private void updateTaskIdAttributeContainer() {
- mTaskIdAttributeContainer = new TaskIdAttributeContainer[mTasks.size()];
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id);
- mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
- }
- }
-
- private TaskIdAttributeContainer createAttributeContainer(Task task,
- TaskThumbnailViewDeprecated thumbnailView) {
- return new TaskIdAttributeContainer(task, thumbnailView, mIconView,
- STAGE_POSITION_UNDEFINED);
- }
-
- @Nullable
- @Override
- public Task getTask() {
- // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
- return mTasks.size() > 0 ? mTasks.get(0) : null;
- }
-
- @Override
- public TaskThumbnailViewDeprecated getThumbnail() {
- // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
- Task task = getTask();
- if (task != null) {
- return mSnapshotViewMap.get(task.key.id);
- }
- // Return the place holder snapshot views. Callers expect this to be non-null
- return mTaskThumbnailViewDeprecated;
- }
-
@Override
public void onTaskListVisibilityChanged(boolean visible, int changes) {
cancelPendingLoadTasks();
@@ -256,15 +206,12 @@
TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- for (Task task : mTasks) {
+ for (TaskContainer container : mTaskContainers) {
CancellableTask<?> thumbLoadRequest =
- thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
- TaskThumbnailViewDeprecated thumbnailView =
- mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(task, thumbnailData);
- }
- });
+ thumbnailCache.updateThumbnailInBackground(container.getTask(),
+ thumbnailData -> container.getThumbnailView().setThumbnail(
+ container.getTask(),
+ thumbnailData));
if (thumbLoadRequest != null) {
mPendingThumbnailRequests.add(thumbLoadRequest);
}
@@ -272,13 +219,10 @@
}
} else {
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- for (Task task : mTasks) {
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(null, null);
- }
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setThumbnail(null, null);
// Reset the task thumbnail ref
- task.thumbnail = null;
+ container.getTask().thumbnail = null;
}
}
}
@@ -327,42 +271,19 @@
}
@Override
- public boolean isDesktopTask() {
- return true;
- }
-
- @Override
void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
// Sets new thumbnails based on the incoming data and refreshes the rest.
- // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
- SparseArray<TaskThumbnailViewDeprecated> thumbnailsToRefresh = mSnapshotViewMap.clone();
if (thumbnailDatas != null) {
- for (Task task : mTasks) {
- int key = task.key.id;
- TaskThumbnailViewDeprecated thumbnailView = thumbnailsToRefresh.get(key);
- ThumbnailData thumbnailData = thumbnailDatas.get(key);
- if (thumbnailView != null && thumbnailData != null) {
- thumbnailView.setThumbnail(task, thumbnailData);
- // Remove this thumbnail from the list that should be refreshed.
- thumbnailsToRefresh.remove(key);
+ for (TaskContainer container : mTaskContainers) {
+ ThumbnailData thumbnailData = thumbnailDatas.get(container.getTask().key.id);
+ if (thumbnailData != null) {
+ container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
+ } else {
+ // Refresh the rest that were not updated.
+ container.getThumbnailView().refresh();
}
}
}
-
- // Refresh the rest that were not updated.
- for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
- thumbnailsToRefresh.valueAt(i).refresh();
- }
- }
-
- @Override
- public TaskThumbnailViewDeprecated[] getThumbnails() {
- TaskThumbnailViewDeprecated[] thumbnails =
- new TaskThumbnailViewDeprecated[mSnapshotViewMap.size()];
- for (int i = 0; i < thumbnails.length; i++) {
- thumbnails[i] = mSnapshotViewMap.valueAt(i);
- }
- return thumbnails;
}
@Override
@@ -370,11 +291,8 @@
resetPersistentViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
- for (Task task : mTasks) {
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id);
- if (thumbnailView != null) {
- thumbnailView.setThumbnail(task, null);
- }
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setThumbnail(container.getTask(), null);
}
setOverlayEnabled(false);
onTaskListVisibilityChanged(false);
@@ -392,8 +310,7 @@
int thumbnailTopMarginPx = mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
containerHeight -= thumbnailTopMarginPx;
- int thumbnails = mSnapshotViewMap.size();
- if (thumbnails == 0) {
+ if (mTaskContainers.isEmpty()) {
return;
}
@@ -413,8 +330,8 @@
}
// Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
- for (int i = 0; i < mTasks.size(); i++) {
- Task task = mTasks.get(i);
+ for (TaskContainer container : mTaskContainers) {
+ Task task = container.getTask();
Rect taskSize = task.appBounds;
if (taskSize == null) {
// Default to quarter of the desktop if we did not get app bounds.
@@ -424,7 +341,7 @@
int thumbWidth = (int) (taskSize.width() * scaleWidth);
int thumbHeight = (int) (taskSize.height() * scaleHeight);
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id);
+ TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailView();
if (thumbnailView != null) {
thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));
@@ -466,9 +383,8 @@
} else {
mBackgroundView.setVisibility(VISIBLE);
}
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.valueAt(i);
- thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().getTaskOverlay().setFullscreenProgress(progress);
}
// Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
// oversized and banner would look disproportionately large.
@@ -482,33 +398,30 @@
@Override
protected void updateSnapshotRadius() {
super.updateSnapshotRadius();
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- if (i == 0) {
- // All snapshots share the same params. Only update it with the first snapshot.
- updateFullscreenParams(mSnapshotDrawParams);
- }
- mSnapshotViewMap.valueAt(i).setFullscreenParams(mSnapshotDrawParams);
+ updateFullscreenParams(mSnapshotDrawParams);
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setFullscreenParams(mSnapshotDrawParams);
}
}
@Override
public void setColorTint(float amount, int tintColor) {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setDimAlpha(amount);
}
}
@Override
protected void applyThumbnailSplashAlpha() {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setSplashAlpha(mTaskThumbnailSplashAlpha);
}
}
@Override
void setThumbnailVisibility(int visibility, int taskId) {
- for (int i = 0; i < mSnapshotViewMap.size(); i++) {
- mSnapshotViewMap.valueAt(i).setVisibility(visibility);
+ for (TaskContainer container : mTaskContainers) {
+ container.getThumbnailView().setVisibility(visibility);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 82ba30b..4df9414 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -79,7 +79,7 @@
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
- private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
+ private static final String TAG = "DigitalWellBeingToast";
private final RecentsViewContainer mContainer;
private final TaskView mTaskView;
@@ -320,12 +320,12 @@
(FrameLayout.LayoutParams) mBanner.getLayoutParams();
DeviceProfile deviceProfile = mContainer.getDeviceProfile();
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
- mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
+ mTaskView.getFirstThumbnailView().getLayoutParams()).bottomMargin;
RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
Pair<Float, Float> translations = orientationHandler
.getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
- mTaskView.getThumbnails(), mTask.key.id, mBanner);
+ mTaskView.getThumbnailViews(), mTask.key.id, mBanner);
mSplitOffsetTranslationX = translations.first;
mSplitOffsetTranslationY = translations.second;
updateTranslationY();
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index b2a8503..1ccb764 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -63,9 +63,7 @@
*/
public class GroupedTaskView extends TaskView {
- private static final String TAG = GroupedTaskView.class.getSimpleName();
- @Nullable
- private Task mSecondaryTask;
+ private static final String TAG = "GroupedTaskView";
// TODO(b/336612373): Support new TTV for GroupedTaskView
private TaskThumbnailViewDeprecated mSnapshotView2;
private TaskViewIcon mIconView2;
@@ -92,6 +90,17 @@
mDigitalWellBeingToast2 = new DigitalWellBeingToast(mContainer, this);
}
+ /**
+ * Returns the second task bound to this TaskView.
+ *
+ * @deprecated Use {@link #mTaskContainers} instead.
+ */
+ @Deprecated
+ @Nullable
+ private Task getSecondTask() {
+ return mTaskContainers.size() > 1 ? mTaskContainers.get(1).getTask() : null;
+ }
+
@Override
public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
if (mSplitBoundsConfig == null) {
@@ -141,13 +150,11 @@
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
@Nullable SplitBounds splitBoundsConfig) {
super.bind(primary, orientedState);
- mSecondaryTask = secondary;
- mTaskIdContainer = new int[]{mTaskIdContainer[0], secondary.key.id};
- mTaskIdAttributeContainer = new TaskIdAttributeContainer[]{
- mTaskIdAttributeContainer[0],
- new TaskIdAttributeContainer(secondary, mSnapshotView2,
- mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT)};
- mTaskIdAttributeContainer[0].setStagePosition(
+ mTaskContainers = Arrays.asList(
+ mTaskContainers.get(0),
+ new TaskContainer(secondary, findViewById(R.id.bottomright_snapshot),
+ mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2));
+ mTaskContainers.get(0).setStagePosition(
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
mSnapshotView2.bind(secondary);
mSplitBoundsConfig = splitBoundsConfig;
@@ -169,12 +176,12 @@
public void setUpShowAllInstancesListener() {
// sets up the listener for the left/top task
super.setUpShowAllInstancesListener();
- if (mTaskIdAttributeContainer.length < 2) {
+ if (mTaskContainers.size() < 2) {
return;
}
// right/bottom task's base package name
- String taskPackageName = mTaskIdAttributeContainer[1].getTask().key.getPackageName();
+ String taskPackageName = mTaskContainers.get(1).getTask().key.getPackageName();
// icon of the right/bottom task
View showWindowsView = findViewById(R.id.show_windows_right);
@@ -190,20 +197,21 @@
TaskIconCache iconCache = model.getIconCache();
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(mSecondaryTask,
- thumbnailData -> mSnapshotView2.setThumbnail(
- mSecondaryTask, thumbnailData
+ mThumbnailLoadRequest2 = thumbnailCache.updateThumbnailInBackground(
+ getSecondTask(),
+ thumbnailData -> mSnapshotView2.setThumbnail(getSecondTask(),
+ thumbnailData
));
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask,
+ mIconLoadRequest2 = iconCache.updateIconInBackground(getSecondTask(),
(task) -> {
setIcon(mIconView2, task.icon);
if (enableOverviewIconMenu()) {
setText(mIconView2, task.title);
}
- mDigitalWellBeingToast2.initialize(mSecondaryTask);
+ mDigitalWellBeingToast2.initialize(getSecondTask());
mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig);
});
@@ -213,7 +221,7 @@
mSnapshotView2.setThumbnail(null, null);
// Reset the task thumbnail reference as well (it will be fetched from the cache or
// reloaded next time we need it)
- mSecondaryTask.thumbnail = null;
+ getSecondTask().thumbnail = null;
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView2, null);
@@ -271,7 +279,7 @@
@Nullable
@Override
public RunnableList launchTaskAnimated() {
- if (mTask == null || mSecondaryTask == null) {
+ if (mTaskContainers.isEmpty()) {
return null;
}
@@ -304,8 +312,8 @@
private void launchTaskInternal(@NonNull Consumer<Boolean> callback, boolean isQuickswitch,
boolean launchingExistingTaskView) {
getRecentsView().getSplitSelectController().launchExistingSplitPair(
- launchingExistingTaskView ? this : null, mTask.key.id,
- mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ launchingExistingTaskView ? this : null, getFirstTask().key.id,
+ getSecondTask().key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
callback, isQuickswitch, getSnapPosition());
Log.d(TAG, "launchTaskInternal - launchExistingSplitPair: " + Arrays.toString(
getTaskIds()));
@@ -314,10 +322,10 @@
@Override
void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
super.refreshThumbnails(thumbnailDatas);
- if (mSecondaryTask != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(mSecondaryTask.key.id);
+ if (getSecondTask() != null && thumbnailDatas != null) {
+ final ThumbnailData thumbnailData = thumbnailDatas.get(getSecondTask().key.id);
if (thumbnailData != null) {
- mSnapshotView2.setThumbnail(mSecondaryTask, thumbnailData);
+ mSnapshotView2.setThumbnail(getSecondTask(), thumbnailData);
return;
}
}
@@ -325,11 +333,6 @@
mSnapshotView2.refresh();
}
- @Override
- public TaskThumbnailViewDeprecated[] getThumbnails() {
- return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated, mSnapshotView2};
- }
-
/**
* Returns taskId that split selection was initiated with,
* {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
@@ -350,7 +353,7 @@
// 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;
+ return initSplitTaskId == getFirstTask().key.id ? 1 : 0;
}
}
@@ -371,7 +374,7 @@
@Override
public void onRecycle() {
super.onRecycle();
- mSnapshotView2.setThumbnail(mSecondaryTask, null);
+ mSnapshotView2.setThumbnail(getSecondTask(), null);
mSplitBoundsConfig = null;
}
@@ -404,9 +407,8 @@
} else {
// Currently being split with this taskView, let the non-split selected thumbnail
// take up full thumbnail area
- Optional<TaskIdAttributeContainer> nonSplitContainer = Arrays.stream(
- mTaskIdAttributeContainer).filter(
- container -> container.getTask().key.id != initSplitTaskId).findAny();
+ Optional<TaskContainer> nonSplitContainer = mTaskContainers.stream().filter(
+ container -> container.getTask().key.id != initSplitTaskId).findAny();
nonSplitContainer.ifPresent(
taskIdAttributeContainer -> taskIdAttributeContainer.getThumbnailView().measure(
widthMeasureSpec, MeasureSpec.makeMeasureSpec(
@@ -492,10 +494,10 @@
}
private void updateSecondaryDwbPlacement() {
- if (mSecondaryTask == null) {
+ if (getSecondTask() == null) {
return;
}
- mDigitalWellBeingToast2.initialize(mSecondaryTask);
+ mDigitalWellBeingToast2.initialize(getSecondTask());
}
@Override
@@ -548,17 +550,11 @@
*/
@Override
void setThumbnailVisibility(int visibility, int taskId) {
- if (visibility == VISIBLE) {
- mTaskThumbnailViewDeprecated.setVisibility(visibility);
- mDigitalWellBeingToast.setBannerVisibility(visibility);
- mSnapshotView2.setVisibility(visibility);
- mDigitalWellBeingToast2.setBannerVisibility(visibility);
- } else if (mTaskIdContainer.length > 0 && mTaskIdContainer[0] == taskId) {
- mTaskThumbnailViewDeprecated.setVisibility(visibility);
- mDigitalWellBeingToast.setBannerVisibility(visibility);
- } else {
- mSnapshotView2.setVisibility(visibility);
- mDigitalWellBeingToast2.setBannerVisibility(visibility);
+ for (TaskContainer container : mTaskContainers) {
+ if (visibility == VISIBLE || container.getTask().key.id == taskId) {
+ container.getThumbnailView().setVisibility(visibility);
+ container.getDigitalWellBeingToast().setBannerVisibility(visibility);
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 5188d4a..ba60141 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -20,6 +20,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -33,22 +34,35 @@
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
+import com.android.quickstep.util.AppPairsController;
import com.android.quickstep.util.LayoutUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.function.Consumer;
/**
* View for showing action buttons in Overview
*/
public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
implements OnClickListener, Insettable {
+ public static final FloatProperty<Consumer<Float>> FLOAT_SETTER =
+ new FloatProperty<>("floatSetter") {
+ @Override
+ public void setValue(Consumer<Float> consumer, float v) {
+ consumer.accept(v);
+ }
+
+ @Override
+ public Float get(Consumer<Float> consumer) {
+ return -1f;
+ }
+ };
private final Rect mInsets = new Rect();
@@ -89,30 +103,24 @@
private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
private static final int INDEX_SHARE_TARGET_ALPHA = 4;
private static final int INDEX_SCROLL_ALPHA = 5;
- private static final int NUM_ALPHAS = 6;
-
- public @interface ScreenshotButtonHiddenFlags { }
- public static final int FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT = 1 << 0;
+ private static final int INDEX_GROUPED_ALPHA = 6;
+ private static final int INDEX_3P_LAUNCHER = 7;
+ private static final int NUM_ALPHAS = 8;
public @interface SplitButtonHiddenFlags { }
public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
- public static final int FLAG_MULTIPLE_TASKS_HIDE_SPLIT = 1 << 1;
- public @interface SplitButtonDisabledFlags { }
- public static final int FLAG_SINGLE_TASK_DISABLE_SPLIT = 1 << 0;
+ /** Holds MultiValueAlpha values for all actions buttons */
+ private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2];
+ /** Index used for single-task actions in the mMultiValueAlphas array */
+ private static final int ACTIONS_ALPHAS = 0;
+ /** Index used for grouped-task actions in the mMultiValueAlphas array */
+ private static final int GROUP_ACTIONS_ALPHAS = 1;
- public @interface AppPairButtonHiddenFlags { }
- public static final int FLAG_SINGLE_TASK_HIDE_APP_PAIR = 1 << 0;
- public static final int FLAG_SMALL_SCREEN_HIDE_APP_PAIR = 1 << 1;
- public static final int FLAG_3P_LAUNCHER_HIDE_APP_PAIR = 1 << 2;
-
- private MultiValueAlpha mMultiValueAlpha;
-
+ /** Container for the action buttons below a focused, non-split Overview tile. */
protected LinearLayout mActionButtons;
- // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is an
- // ImageButton in go launcher (does not share a common class with Button). Take care when
- // casting this.
- private View mScreenshotButton;
+ /** Container for the action buttons below a focused, split Overview tile. */
+ protected LinearLayout mGroupActionButtons;
private Button mSplitButton;
private Button mSaveAppPairButton;
@@ -122,21 +130,17 @@
@ActionsDisabledFlags
protected int mDisabledFlags;
- @ScreenshotButtonHiddenFlags
- private int mScreenshotButtonHiddenFlags;
-
@SplitButtonHiddenFlags
private int mSplitButtonHiddenFlags;
- @AppPairButtonHiddenFlags
- private int mAppPairButtonHiddenFlags;
-
@Nullable
protected T mCallbacks;
@Nullable
protected DeviceProfile mDp;
private final Rect mTaskSize = new Rect();
+ private boolean mIsGroupedTask = false;
+ private boolean mCanSaveAppPair = false;
public OverviewActionsView(Context context) {
this(context, null);
@@ -153,12 +157,21 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ // Initialize 2 view containers: one for single tasks, one for grouped tasks.
+ // These will take up the same space on the screen and alternate visibility as needed.
mActionButtons = findViewById(R.id.action_buttons);
- mMultiValueAlpha = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
- mMultiValueAlpha.setUpdateVisibility(true);
+ mGroupActionButtons = findViewById(R.id.group_action_buttons);
+ // Initialize a list to set alpha on mActionButtons and mGroupActionButtons simultaneously.
+ mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
+ mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
+ new MultiValueAlpha(mGroupActionButtons, NUM_ALPHAS);
+ Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true));
- mScreenshotButton = findViewById(R.id.action_screenshot);
- mScreenshotButton.setOnClickListener(this);
+ // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is
+ // an ImageButton in go launcher (does not share a common class with Button). Take care when
+ // casting this.
+ View screenshotButton = findViewById(R.id.action_screenshot);
+ screenshotButton.setOnClickListener(this);
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
@@ -209,7 +222,7 @@
mHiddenFlags &= ~visibilityFlags;
}
boolean isHidden = mHiddenFlags != 0;
- mMultiValueAlpha.get(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
+ setActionsAlpha(INDEX_HIDDEN_FLAGS_ALPHA, isHidden ? 0 : 1);
}
/**
@@ -234,14 +247,13 @@
* Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
* is focused.
* @param isGroupedTask True if the focused task is a grouped task.
+ * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app
+ * pair.
*/
- public void updateForGroupedTask(boolean isGroupedTask) {
- // Update flags to see if split button should be hidden.
- updateSplitButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SPLIT, isGroupedTask);
- // Update flags to see if screenshot button should be hidden.
- updateScreenshotButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT, isGroupedTask);
- // Update flags to see if save app pair button should be hidden.
- updateAppPairButtonHiddenFlags(FLAG_SINGLE_TASK_HIDE_APP_PAIR, !isGroupedTask);
+ public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
+ mIsGroupedTask = isGroupedTask;
+ mCanSaveAppPair = canSaveAppPair;
+ updateActionButtonsVisibility();
}
/**
@@ -251,36 +263,30 @@
assert mDp != null;
// Update flags to see if split button should be hidden.
updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
- // Update flags to see if save app pair button should be hidden.
- updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, !mDp.isTablet);
+ updateActionButtonsVisibility();
+ }
+
+ private void updateActionButtonsVisibility() {
+ assert mDp != null;
+ boolean showSingleTaskActions = !mIsGroupedTask;
+ boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
+ getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
+ getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
}
/**
* Updates flags to hide and show actions buttons for 1p/3p launchers.
*/
public void updateFor3pLauncher(boolean is3pLauncher) {
- updateAppPairButtonHiddenFlags(FLAG_3P_LAUNCHER_HIDE_APP_PAIR, is3pLauncher);
+ getGroupActionsAlphas().get(INDEX_3P_LAUNCHER).setValue(is3pLauncher ? 0 : 1);
}
- /**
- * Updates the proper flags to indicate whether the "Screenshot" button should be hidden.
- *
- * @param flag The flag to update.
- * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
- */
- private void updateScreenshotButtonHiddenFlags(@ScreenshotButtonHiddenFlags int flag,
- boolean enable) {
- if (mScreenshotButton == null) return;
- if (enable) {
- mScreenshotButtonHiddenFlags |= flag;
- } else {
- mScreenshotButtonHiddenFlags &= ~flag;
- }
- int desiredVisibility = mScreenshotButtonHiddenFlags == 0 ? VISIBLE : GONE;
- if (mScreenshotButton.getVisibility() != desiredVisibility) {
- mScreenshotButton.setVisibility(desiredVisibility);
- mActionButtons.requestLayout();
- }
+ private MultiValueAlpha getActionsAlphas() {
+ return mMultiValueAlphas[ACTIONS_ALPHAS];
+ }
+
+ private MultiValueAlpha getGroupActionsAlphas() {
+ return mMultiValueAlphas[GROUP_ACTIONS_ALPHAS];
}
/**
@@ -304,56 +310,36 @@
}
}
- /**
- * Updates the proper flags to indicate whether the "Save app pair" button should be disabled.
- *
- * @param flag The flag to update.
- * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
- */
- private void updateAppPairButtonHiddenFlags(
- @AppPairButtonHiddenFlags int flag, boolean enable) {
- if (!FeatureFlags.enableAppPairs()) {
- return;
- }
-
- if (mSaveAppPairButton == null) return;
- if (enable) {
- mAppPairButtonHiddenFlags |= flag;
- } else {
- mAppPairButtonHiddenFlags &= ~flag;
- }
- int desiredVisibility = mAppPairButtonHiddenFlags == 0 ? VISIBLE : GONE;
- if (mSaveAppPairButton.getVisibility() != desiredVisibility) {
- mSaveAppPairButton.setVisibility(desiredVisibility);
- mActionButtons.requestLayout();
- }
+ private void setActionsAlpha(int index, float value) {
+ Arrays.stream(mMultiValueAlphas).forEach(a -> a.get(index).setValue(value));
}
- public MultiProperty getContentAlpha() {
- return mMultiValueAlpha.get(INDEX_CONTENT_ALPHA);
+ public Consumer<Float> getContentAlphaSetter() {
+ return v -> setActionsAlpha(INDEX_CONTENT_ALPHA, v);
}
- public MultiProperty getVisibilityAlpha() {
- return mMultiValueAlpha.get(INDEX_VISIBILITY_ALPHA);
+ public Consumer<Float> getVisibilityAlphaSetter() {
+ return v -> setActionsAlpha(INDEX_VISIBILITY_ALPHA, v);
}
- public MultiProperty getFullscreenAlpha() {
- return mMultiValueAlpha.get(INDEX_FULLSCREEN_ALPHA);
+ public Consumer<Float> getFullscreenAlphaSetter() {
+ return v -> setActionsAlpha(INDEX_FULLSCREEN_ALPHA, v);
}
- public MultiProperty getShareTargetAlpha() {
- return mMultiValueAlpha.get(INDEX_SHARE_TARGET_ALPHA);
+ public Consumer<Float> getShareTargetAlphaSetter() {
+ return v -> setActionsAlpha(INDEX_SHARE_TARGET_ALPHA, v);
}
- public MultiProperty getIndexScrollAlpha() {
- return mMultiValueAlpha.get(INDEX_SCROLL_ALPHA);
+ public Consumer<Float> getIndexScrollAlphaSetter() {
+ return v -> setActionsAlpha(INDEX_SCROLL_ALPHA, v);
}
/**
* Returns the visibility of the overview actions buttons.
*/
- public @Visibility int getActionsButtonVisibility() {
- return mActionButtons.getVisibility();
+ public boolean areActionsButtonsVisible() {
+ return mActionButtons.getVisibility() == View.VISIBLE
+ || mGroupActionButtons.getVisibility() == View.VISIBLE;
}
/**
@@ -366,10 +352,17 @@
/** Updates vertical margins for different navigation mode or configuration changes. */
public void updateVerticalMargin(NavigationMode mode) {
+ updateActionBarPosition(mActionButtons);
+ updateActionBarPosition(mGroupActionButtons);
+ }
+
+ /** Positions actions buttons according to device settings and insets. */
+ private void updateActionBarPosition(LinearLayout actionBar) {
if (mDp == null) {
return;
}
- LayoutParams actionParams = (LayoutParams) mActionButtons.getLayoutParams();
+
+ LayoutParams actionParams = (LayoutParams) actionBar.getLayoutParams();
actionParams.setMargins(
actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
actionParams.rightMargin, getBottomMargin());
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 077cd1b..a242e1d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,6 +35,7 @@
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -186,6 +187,7 @@
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.recents.viewmodel.RecentsViewData;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimUtils;
@@ -204,7 +206,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.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -376,6 +378,9 @@
public void setValue(RecentsView view, float scale) {
view.setScaleX(scale);
view.setScaleY(scale);
+ if (enableRefactorTaskThumbnail()) {
+ view.mRecentsViewData.getScale().setValue(scale);
+ }
view.mLastComputedTaskStartPushOutDistance = null;
view.mLastComputedTaskEndPushOutDistance = null;
view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
@@ -446,6 +451,8 @@
private static final float FOREGROUND_SCRIM_TINT = 0.32f;
+ public final RecentsViewData mRecentsViewData = new RecentsViewData();
+
protected final RecentsOrientedState mOrientationState;
protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
@Nullable
@@ -591,7 +598,7 @@
if (taskView == null) {
return;
}
- Task.TaskKey taskKey = taskView.getTask().key;
+ Task.TaskKey taskKey = taskView.getFirstTask().key;
UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
@@ -991,8 +998,7 @@
if (mHandleTaskStackChanges) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
- for (TaskIdAttributeContainer container :
- taskView.getTaskIdAttributeContainers()) {
+ for (TaskContainer container : taskView.getTaskContainers()) {
if (container == null || taskId != container.getTask().key.id) {
continue;
}
@@ -1007,11 +1013,12 @@
public void onTaskIconChanged(String pkg, UserHandle user) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = requireTaskViewAt(i);
- Task task = tv.getTask();
+ Task task = tv.getFirstTask();
if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
&& task.key.userId == user.getIdentifier()) {
task.icon = null;
- if (tv.getIconView().getDrawable() != null) {
+ TaskViewIcon firstIconView = tv.getFirstIconView();
+ if (firstIconView != null && firstIconView.getDrawable() != null) {
tv.onTaskListVisibilityChanged(true /* visible */);
}
}
@@ -1042,7 +1049,7 @@
continue;
}
// taskView could be a GroupedTaskView, so select the relevant task by ID
- TaskIdAttributeContainer taskAttributes = taskView.getTaskAttributesById(id);
+ TaskContainer taskAttributes = taskView.getTaskContainerById(id);
if (taskAttributes == null) {
continue;
}
@@ -1731,7 +1738,7 @@
int[] currentTaskIds;
TaskView currentTaskView = getTaskViewAt(mCurrentPage);
- if (currentTaskView != null && currentTaskView.getTask() != null) {
+ if (currentTaskView != null && currentTaskView.getFirstTask() != null) {
currentTaskIds = currentTaskView.getTaskIds();
} else {
currentTaskIds = new int[0];
@@ -2012,6 +2019,9 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewData.getFullscreenProgress().setValue(mFullscreenProgress);
+ }
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
requireTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
@@ -2019,7 +2029,7 @@
mClearAllButton.setFullscreenProgress(fullscreenProgress);
// Fade out the actions view quickly (0.1 range)
- mActionsView.getFullscreenAlpha().setValue(
+ mActionsView.getFullscreenAlphaSetter().accept(
mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR));
}
@@ -2270,8 +2280,8 @@
}
private void animateActionsViewAlpha(float alphaValue, long duration) {
- mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(
- mActionsView.getVisibilityAlpha(), MULTI_PROPERTY_VALUE, alphaValue);
+ mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlphaSetter(),
+ OverviewActionsView.FLOAT_SETTER, alphaValue);
mActionsViewAlphaAnimatorFinalValue = alphaValue;
mActionsViewAlphaAnimator.setDuration(duration);
// Set autocancel to prevent race-conditiony setting of alpha from other animations
@@ -2290,7 +2300,7 @@
mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled);
// Clear all button alpha was set by the previous line.
- mActionsView.getIndexScrollAlpha().setValue(1 - mClearAllButton.getScrollAlpha());
+ mActionsView.getIndexScrollAlphaSetter().accept(1 - mClearAllButton.getScrollAlpha());
}
@Override
@@ -2354,8 +2364,8 @@
// Update the task data for the in/visible children
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
- TaskIdAttributeContainer[] containers = taskView.getTaskIdAttributeContainers();
- if (containers.length == 0) {
+ List<TaskContainer> containers = taskView.getTaskContainers();
+ if (containers.isEmpty()) {
continue;
}
int index = indexOfChild(taskView);
@@ -2367,8 +2377,8 @@
}
if (visible) {
// Default update all non-null tasks, then remove running ones
- List<Task> tasksToUpdate = Arrays.stream(containers).filter(Objects::nonNull)
- .map(TaskIdAttributeContainer::getTask)
+ List<Task> tasksToUpdate = containers.stream()
+ .map(TaskContainer::getTask)
.collect(Collectors.toCollection(ArrayList::new));
if (mTmpRunningTasks != null) {
for (Task t : mTmpRunningTasks) {
@@ -2393,7 +2403,7 @@
mHasVisibleTaskData.put(task.key.id, visible);
}
} else {
- for (TaskIdAttributeContainer container : containers) {
+ for (TaskContainer container : containers) {
if (container == null) {
continue;
}
@@ -3816,7 +3826,7 @@
if (success) {
if (shouldRemoveTask) {
- if (dismissedTaskView.getTask() != null) {
+ if (dismissedTaskView.getFirstTask() != null) {
if (dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> removeTaskInternal(dismissedTaskViewId));
@@ -4011,7 +4021,9 @@
* * Device is large screen
*/
private void updateCurrentTaskActionsVisibility() {
- boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
+ TaskView taskView = getCurrentPageTaskView();
+ boolean isCurrentSplit = taskView instanceof GroupedTaskView;
+ GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
// Update flags to see if entire actions bar should be hidden.
if (!FeatureFlags.enableAppPairs()) {
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
@@ -4019,9 +4031,11 @@
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
// Update flags to see if actions bar should show buttons for a single task or a pair of
// tasks.
- mActionsView.updateForGroupedTask(isCurrentSplit);
+ boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
+ getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
+ mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);
- boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
+ boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
@@ -4295,7 +4309,7 @@
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
- mActionsView.getContentAlpha().setValue(mContentAlpha);
+ mActionsView.getContentAlphaSetter().accept(mContentAlpha);
if (alpha > 0) {
setVisibility(VISIBLE);
@@ -4730,7 +4744,7 @@
*/
public void resetModalVisuals() {
if (mSelectedTask != null) {
- mSelectedTask.getThumbnail().getTaskOverlay().resetModalVisuals();
+ mSelectedTask.getFirstThumbnailView().getTaskOverlay().resetModalVisuals();
}
}
@@ -4749,7 +4763,7 @@
StatsLogManager.EventEnum splitEvent) {
mSplitHiddenTaskView = taskView;
mSplitSelectStateController.setInitialTaskSelect(null /*intent*/,
- stagePosition, taskView.getItemInfo(), splitEvent, taskView.mTask.key.id);
+ stagePosition, taskView.getItemInfo(), splitEvent, taskView.getFirstTask().key.id);
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -4800,11 +4814,11 @@
// Animate pair thumbnail into full thumbnail
boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
== mSplitSelectStateController.getInitialTaskId();
- TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
- .getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
- TaskThumbnailViewDeprecated thumbnail = taskIdAttributeContainer.getThumbnailView();
+ TaskContainer taskContainer = mSplitHiddenTaskView
+ .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+ TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailView();
mSplitSelectStateController.getSplitAnimationController()
- .addInitialSplitFromPair(taskIdAttributeContainer, builder,
+ .addInitialSplitFromPair(taskContainer, builder,
mContainer.getDeviceProfile(),
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
primaryTaskSelected);
@@ -5202,7 +5216,7 @@
updateGridProperties();
updateScrollSynchronously();
- int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ int targetSysUiFlags = tv.getFirstThumbnailView().getSysUiStatusNavFlags();
final boolean[] passedOverviewThreshold = new boolean[] {false};
ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
progressAnim.addUpdateListener(animator -> {
@@ -5266,7 +5280,7 @@
} else {
tv.launchTask(this::onTaskLaunchAnimationEnd);
}
- Task task = tv.getTask();
+ Task task = tv.getFirstTask();
if (task != null) {
mContainer.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
@@ -5899,7 +5913,7 @@
}
taskView.setShowScreenshot(true);
- for (TaskIdAttributeContainer container : taskView.getTaskIdAttributeContainers()) {
+ for (TaskContainer container : taskView.getTaskContainers()) {
if (container == null) {
continue;
}
@@ -6269,7 +6283,7 @@
/**
* Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded.
*/
- public void moveTaskToDesktop(TaskIdAttributeContainer taskContainer,
+ public void moveTaskToDesktop(TaskContainer taskContainer,
Runnable successCallback) {
if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
return;
@@ -6278,7 +6292,7 @@
() -> moveTaskToDesktopInternal(taskContainer, successCallback)));
}
- private void moveTaskToDesktopInternal(TaskIdAttributeContainer taskContainer,
+ private void moveTaskToDesktopInternal(TaskContainer taskContainer,
Runnable successCallback) {
if (mDesktopRecentsTransitionController == null) {
return;
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 443f83c..578d471 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -18,8 +18,6 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA;
@@ -56,7 +54,7 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
+import com.android.quickstep.views.TaskView.TaskContainer;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -76,7 +74,7 @@
private ValueAnimator mRevealAnimator;
@Nullable private Runnable mOnClosingStartCallback;
private TaskView mTaskView;
- private TaskIdAttributeContainer mTaskContainer;
+ private TaskContainer mTaskContainer;
private LinearLayout mOptionLayout;
private float mMenuTranslationYBeforeOpen;
private float mMenuTranslationXBeforeOpen;
@@ -163,7 +161,10 @@
}
}
- public static boolean showForTask(TaskIdAttributeContainer taskContainer,
+ /**
+ * Show a task menu for the given taskContainer.
+ */
+ public static boolean showForTask(TaskContainer taskContainer,
@Nullable Runnable onClosingStartCallback) {
RecentsViewContainer container = RecentsViewContainer.containerFromContext(
taskContainer.getTaskView().getContext());
@@ -173,11 +174,14 @@
return taskMenuView.populateAndShowForTask(taskContainer);
}
- public static boolean showForTask(TaskIdAttributeContainer taskContainer) {
+ /**
+ * Show a task menu for the given taskContainer.
+ */
+ public static boolean showForTask(TaskContainer taskContainer) {
return showForTask(taskContainer, null);
}
- private boolean populateAndShowForTask(TaskIdAttributeContainer taskContainer) {
+ private boolean populateAndShowForTask(TaskContainer taskContainer) {
if (isAttachedToWindow()) {
return false;
}
@@ -198,7 +202,7 @@
return true;
}
- private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
+ private void addMenuOptions(TaskContainer taskContainer) {
if (enableOverviewIconMenu()) {
removeView(mTaskName);
} else {
@@ -226,7 +230,7 @@
mOptionLayout.addView(menuOptionView);
}
- private void orientAroundTaskView(TaskIdAttributeContainer taskContainer) {
+ private void orientAroundTaskView(TaskContainer taskContainer) {
RecentsView recentsView = mContainer.getOverviewPanel();
RecentsPagedOrientationHandler orientationHandler =
recentsView.getPagedOrientationHandler();
@@ -301,7 +305,6 @@
private void animateOpenOrClosed(boolean closing) {
if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE, "getting canceled");
mOpenCloseAnimator.cancel();
}
mOpenCloseAnimator = new AnimatorSet();
@@ -358,14 +361,10 @@
iconAppChip.getMenuTranslationX(),
MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
menuTranslationXAnim.setInterpolator(EMPHASIZED);
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: running translation animations");
mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim,
menuTranslationXAnim, menuTranslationYAnim);
}
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: running animation 2");
mOpenCloseAnimator.playTogether(mRevealAnimator,
ObjectAnimator.ofFloat(
mTaskContainer.getThumbnailView(), DIM_ALPHA,
@@ -374,8 +373,6 @@
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: onAnimationStart");
setVisibility(VISIBLE);
if (closing && mOnClosingStartCallback != null) {
mOnClosingStartCallback.run();
@@ -383,16 +380,7 @@
}
@Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: onAnimationCancel");
- }
-
- @Override
public void onAnimationSuccess(Animator animator) {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
- "TaskMenuView.java.animateOpenOrClosed: onAnimationSuccess");
if (closing) {
closeComplete();
}
@@ -403,7 +391,6 @@
}
private void closeComplete() {
- testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE, "TaskMenuView.java.closeComplete");
mIsOpen = false;
mContainer.getDragLayer().removeView(this);
mRevealAnimator = null;
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index a138db0..7adc32e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -37,14 +37,14 @@
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.util.Themes
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
class TaskMenuViewWithArrow<T> : ArrowPopup<T> where T : RecentsViewContainer, T : Context {
companion object {
const val TAG = "TaskMenuViewWithArrow"
fun <T> showForTask(
- taskContainer: TaskIdAttributeContainer,
+ taskContainer: TaskContainer,
alignedOptionIndex: Int = 0
): Boolean where T : RecentsViewContainer, T : Context {
val container: RecentsViewContainer =
@@ -87,7 +87,7 @@
private lateinit var taskView: TaskView
private lateinit var optionLayout: LinearLayout
- private lateinit var taskContainer: TaskIdAttributeContainer
+ private lateinit var taskContainer: TaskContainer
private var optionMeasuredHeight = 0
private val arrowHorizontalPadding: Int
@@ -141,7 +141,7 @@
}
private fun populateAndShowForTask(
- taskContainer: TaskIdAttributeContainer,
+ taskContainer: TaskContainer,
alignedOptionIndex: Int
): Boolean {
if (isAttachedToWindow) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 9802beb..21c6ca8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -53,6 +53,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
+import com.android.launcher3.util.ViewPool;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
@@ -66,7 +67,7 @@
* @deprecated This class will be replaced by the new [TaskThumbnailView].
*/
@Deprecated
-public class TaskThumbnailViewDeprecated extends View {
+public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
new MainThreadInitializedObject<>(FullscreenDrawParams::new);
@@ -606,4 +607,9 @@
}
return mThumbnailData.isRealSnapshot && !mTask.isLocked;
}
+
+ @Override
+ public void onRecycle() {
+ // Do nothing
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index f789686..1187222 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -107,6 +107,7 @@
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.task.thumbnail.TaskThumbnail;
import com.android.quickstep.task.thumbnail.TaskThumbnailView;
+import com.android.quickstep.task.viewmodel.TaskViewData;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.BorderAnimator;
import com.android.quickstep.util.RecentsOrientedState;
@@ -135,7 +136,7 @@
*/
public class TaskView extends FrameLayout implements Reusable {
- private static final String TAG = TaskView.class.getSimpleName();
+ private static final String TAG = "TaskView";
public static final int FLAG_UPDATE_ICON = 1;
public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
@@ -326,9 +327,7 @@
}
};
- @Nullable
- protected Task mTask;
- @Nullable // can be null when enableRefactorTaskThumbnail() == true
+ public TaskViewData mTaskViewData = new TaskViewData();
protected TaskThumbnailViewDeprecated mTaskThumbnailViewDeprecated;
protected TaskThumbnailView mTaskThumbnailView;
protected TaskViewIcon mIconView;
@@ -371,9 +370,7 @@
private float mStableAlpha = 1;
private int mTaskViewId = -1;
- protected int[] mTaskIdContainer = new int[0];
- protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
- new TaskIdAttributeContainer[0];
+ protected List<TaskContainer> mTaskContainers = Collections.emptyList();
private boolean mShowScreenshot;
private boolean mBorderEnabled;
@@ -501,16 +498,19 @@
public void notifyIsRunningTaskUpdated() {
// TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
// so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
- if (mTask != null) {
+ if (!mTaskContainers.isEmpty()) {
bindTaskThumbnailView();
}
}
/**
- * Builds proto for logging
+ * Builds proto for logging.
+ *
+ * @deprecated Use {@link #getItemInfo(Task)} instead.
*/
+ @Deprecated
public WorkspaceItemInfo getItemInfo() {
- return getItemInfo(mTask);
+ return getItemInfo(getFirstTask());
}
/**
@@ -687,11 +687,9 @@
*/
public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
- mTask = task;
- mTaskIdContainer = new int[]{mTask.key.id};
- mTaskIdAttributeContainer = new TaskIdAttributeContainer[]{
- new TaskIdAttributeContainer(task, mTaskThumbnailViewDeprecated, mIconView,
- STAGE_POSITION_UNDEFINED)};
+ mTaskContainers = Collections.singletonList(
+ new TaskContainer(task, mTaskThumbnailViewDeprecated, mIconView,
+ STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast));
if (enableRefactorTaskThumbnail()) {
bindTaskThumbnailView();
} else {
@@ -703,17 +701,17 @@
private void bindTaskThumbnailView() {
// TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
// so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
- mTaskThumbnailView.getViewModel().bind(new TaskThumbnail(mTask, isRunningTask()));
+ mTaskThumbnailView.getViewModel().bind(new TaskThumbnail(getFirstTask(), isRunningTask()));
}
/**
* Sets up an on-click listener and the visibility for show_windows icon on top of the task.
*/
public void setUpShowAllInstancesListener() {
- if (mTaskIdAttributeContainer.length == 0) {
+ if (mTaskContainers.isEmpty()) {
return;
}
- String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName();
+ String taskPackageName = mTaskContainers.get(0).getTask().key.getPackageName();
// icon of the top/left task
View showWindowsView = findViewById(R.id.show_windows);
@@ -755,50 +753,62 @@
filterView.setOnClickListener(callback);
}
- public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
- return mTaskIdAttributeContainer;
+ /**
+ * Returns a list of all TaskContainers in the TaskView.
+ */
+ public List<TaskContainer> getTaskContainers() {
+ return mTaskContainers;
}
+ /**
+ * Returns the first task bound to this TaskView.
+ *
+ * @deprecated Use {@link #mTaskContainers} instead.
+ */
+ @Deprecated
@Nullable
- public Task getTask() {
- return mTask;
+ public Task getFirstTask() {
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getTask() : null;
}
/**
* Check if given {@code taskId} is tracked in this view
*/
public boolean containsTaskId(int taskId) {
- return Arrays.stream(mTaskIdContainer).anyMatch(myTaskId -> myTaskId == taskId);
+ return getTaskContainerById(taskId) != null;
}
/**
* Returns a copy of integer array containing taskIds of all tasks in the TaskView.
*/
public int[] getTaskIds() {
- return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length);
+ return mTaskContainers.stream().mapToInt(
+ container -> container.getTask().key.id).toArray();
}
public boolean containsMultipleTasks() {
- return mTaskIdContainer.length > 1;
+ return mTaskContainers.size() > 1;
}
/**
- * Returns the TaskIdAttributeContainer corresponding to a given taskId, or null if the TaskView
- * does not contain a Task with that ID.
+ * Returns the TaskContainer corresponding to a given taskId, or null if the TaskView does
+ * not contain a Task with that ID.
*/
@Nullable
- public TaskIdAttributeContainer getTaskAttributesById(int taskId) {
- for (TaskIdAttributeContainer attributes : mTaskIdAttributeContainer) {
- if (attributes.getTask().key.id == taskId) {
- return attributes;
- }
- }
-
- return null;
+ public TaskContainer getTaskContainerById(int taskId) {
+ return mTaskContainers.stream().filter(
+ container -> container.getTask().key.id == taskId).findFirst().orElse(null);
}
- public TaskThumbnailViewDeprecated getThumbnail() {
- return mTaskThumbnailViewDeprecated;
+ /**
+ * Returns the first thumbnailView of the TaskView.
+ *
+ * @deprecated Use {@link #mTaskContainers} instead.
+ */
+ @Deprecated
+ public TaskThumbnailViewDeprecated getFirstThumbnailView() {
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getThumbnailView()
+ : mTaskThumbnailViewDeprecated;
}
void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
@@ -806,10 +816,10 @@
// TODO(b/334825222) add thumbnail logic
return;
}
- if (mTask != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id);
+ if (getFirstTask() != null && thumbnailDatas != null) {
+ final ThumbnailData thumbnailData = thumbnailDatas.get(getFirstTask().key.id);
if (thumbnailData != null) {
- mTaskThumbnailViewDeprecated.setThumbnail(mTask, thumbnailData);
+ mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(), thumbnailData);
return;
}
}
@@ -817,19 +827,27 @@
mTaskThumbnailViewDeprecated.refresh();
}
- /** TODO(b/197033698) Remove all usages of above method and migrate to this one */
- public TaskThumbnailViewDeprecated[] getThumbnails() {
- return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated};
+ public TaskThumbnailViewDeprecated[] getThumbnailViews() {
+ return mTaskContainers.stream().map(
+ TaskContainer::getThumbnailView).toArray(
+ TaskThumbnailViewDeprecated[]::new);
}
- public TaskViewIcon getIconView() {
- return mIconView;
+ /**
+ * Returns the first iconView of the TaskView.
+ *
+ * @deprecated Use {@link #mTaskContainers} instead.
+ */
+ @Deprecated
+ @Nullable
+ public TaskViewIcon getFirstIconView() {
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getIconView() : null;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
RecentsView recentsView = getRecentsView();
- if (recentsView == null || getTask() == null) {
+ if (recentsView == null || getFirstTask() == null) {
return false;
}
SplitSelectStateController splitSelectStateController =
@@ -837,7 +855,7 @@
// Disable taps for split selection animation unless we have multiple tasks
boolean disableTapsForSplitSelect =
splitSelectStateController.isSplitSelectActive()
- && splitSelectStateController.getInitialTaskId() == getTask().key.id
+ && splitSelectStateController.getInitialTaskId() == getFirstTask().key.id
&& !containsMultipleTasks();
if (disableTapsForSplitSelect) {
return false;
@@ -850,19 +868,21 @@
}
private void onClick(View view) {
- if (getTask() == null) {
+ if (getFirstTask() == null) {
Log.d("b/310064698", "onClick - task is null");
return;
}
if (confirmSecondSplitSelectApp()) {
- Log.d("b/310064698", mTask + " - onClick - split select is active");
+ Log.d("b/310064698",
+ Arrays.toString(getTaskIds()) + " - onClick - split select is active");
return;
}
RunnableList callbackList = launchTasks();
- Log.d("b/310064698", mTask + " - onClick - callbackList: " + callbackList);
+ Log.d("b/310064698",
+ Arrays.toString(getTaskIds()) + " - onClick - callbackList: " + callbackList);
if (callbackList != null) {
- callbackList.add(() -> Log.d("b/310064698", Arrays.toString(
- getTaskIds()) + " - onClick - launchCompleted"));
+ callbackList.add(() -> Log.d("b/310064698",
+ Arrays.toString(getTaskIds()) + " - onClick - launchCompleted"));
}
mContainer.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
@@ -874,10 +894,10 @@
*/
protected boolean confirmSecondSplitSelectApp() {
int index = getLastSelectedChildTaskIndex();
- if (index >= mTaskIdAttributeContainer.length) {
+ if (index >= mTaskContainers.size()) {
return false;
}
- TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
+ TaskContainer container = mTaskContainers.get(index);
if (container != null) {
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView().getDrawable(), container.getThumbnailView(),
@@ -903,14 +923,15 @@
*/
@Nullable
public RunnableList launchTaskAnimated() {
- if (mTask != null) {
+ if (getFirstTask() != null) {
TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
+ TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", Arrays.toString(
+ getTaskIds()));
ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null);
opts.options.setLaunchDisplayId(
getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
if (ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(mTask.key, opts.options)) {
+ .startActivityFromRecents(getFirstTask().key, opts.options)) {
Log.d(TAG, "launchTaskAnimated - startActivityFromRecents: " + Arrays.toString(
getTaskIds()));
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
@@ -938,7 +959,7 @@
return null;
}
} else {
- Log.d(TAG, "launchTaskAnimated - mTask is null" + Arrays.toString(getTaskIds()));
+ Log.d(TAG, "launchTaskAnimated - getTask() is null" + Arrays.toString(getTaskIds()));
return null;
}
}
@@ -954,9 +975,10 @@
* Starts the task associated with this view without any animation
*/
public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- if (mTask != null) {
+ if (getFirstTask() != null) {
TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
+ TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", Arrays.toString(
+ getTaskIds()));
TaskRemovedDuringLaunchListener failureListener = new TaskRemovedDuringLaunchListener(
getContext().getApplicationContext());
@@ -964,7 +986,7 @@
// We only listen for failures to launch in quickswitch because the during this
// gesture launcher is in the background state, vs other launches which are in
// the actual overview state
- failureListener.register(mContainer, mTask.key.id, () -> {
+ failureListener.register(mContainer, getFirstTask().key.id, () -> {
notifyTaskLaunchFailed(TAG);
RecentsView rv = getRecentsView();
if (rv != null) {
@@ -997,7 +1019,7 @@
if (!enableRefactorTaskThumbnail()) {
opts.setDisableStartingWindow(mTaskThumbnailViewDeprecated.shouldShowSplashView());
}
- Task.TaskKey key = mTask.key;
+ Task.TaskKey key = getFirstTask().key;
UI_HELPER_EXECUTOR.execute(() -> {
if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
// If the call to start activity failed, then post the result immediately,
@@ -1013,7 +1035,7 @@
});
} else {
callback.accept(false);
- Log.d(TAG, "launchTask - mTask is null" + Arrays.toString(getTaskIds()));
+ Log.d(TAG, "launchTask - getTask() is null" + Arrays.toString(getTaskIds()));
}
}
@@ -1069,7 +1091,8 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
- if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) {
+ if (getFirstTask() != null
+ && getFirstTask().key.displayId != getRootViewDisplayId()) {
launchTaskAnimated();
}
mIsClickableAsLiveTile = true;
@@ -1110,7 +1133,7 @@
* @param visible If this task view will be visible to the user in overview or hidden
*/
public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
- if (mTask == null) {
+ if (getFirstTask() == null) {
return;
}
cancelPendingLoadTasks();
@@ -1123,15 +1146,16 @@
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
- mTask, thumbnail -> {
+ getFirstTask(), thumbnail -> {
if (!enableRefactorTaskThumbnail()) {
// TODO(b/334825222) add thumbnail state
- mTaskThumbnailViewDeprecated.setThumbnail(mTask, thumbnail);
+ mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(),
+ thumbnail);
}
});
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest = iconCache.updateIconInBackground(mTask,
+ mIconLoadRequest = iconCache.updateIconInBackground(getFirstTask(),
(task) -> {
setIcon(mIconView, task.icon);
if (enableOverviewIconMenu()) {
@@ -1151,7 +1175,7 @@
}
// Reset the task thumbnail reference as well (it will be fetched from the cache or
// reloaded next time we need it)
- mTask.thumbnail = null;
+ getFirstTask().thumbnail = null;
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView, null);
@@ -1195,9 +1219,8 @@
}
protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- Optional<TaskIdAttributeContainer> menuContainer = Arrays.stream(
- mTaskIdAttributeContainer).filter(
- container -> container.getIconView() == iconView).findAny();
+ Optional<TaskContainer> menuContainer = mTaskContainers.stream().filter(
+ container -> container.getIconView() == iconView).findAny();
if (menuContainer.isEmpty()) {
return false;
}
@@ -1270,7 +1293,7 @@
if (!enableRefactorTaskThumbnail()) {
mTaskThumbnailViewDeprecated.getTaskOverlay().updateOrientationState(orientationState);
}
- mDigitalWellBeingToast.initialize(mTask);
+ mDigitalWellBeingToast.initialize(getFirstTask());
}
/**
@@ -1281,11 +1304,6 @@
return deviceProfile.isTablet && !isFocusedTask();
}
- /** Whether this task view represents the desktop */
- public boolean isDesktopTask() {
- return false;
- }
-
/**
* Called to animate a smooth transition when going directly from an app into Overview (and
* vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
@@ -1378,7 +1396,7 @@
if (enableRefactorTaskThumbnail()) {
notifyIsRunningTaskUpdated();
} else {
- mTaskThumbnailViewDeprecated.setThumbnail(mTask, null);
+ mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(), null);
}
setOverlayEnabled(false);
onTaskListVisibilityChanged(false);
@@ -1445,6 +1463,9 @@
scale *= mDismissScale;
setScaleX(scale);
setScaleY(scale);
+ if (enableRefactorTaskThumbnail()) {
+ mTaskViewData.getScale().setValue(scale);
+ }
updateSnapshotRadius();
}
@@ -1675,7 +1696,7 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ for (TaskContainer taskContainer : mTaskContainers) {
for (SystemShortcut s : TraceHelper.allowIpcs(
"TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
info.addAction(s.createAccessibilityAction(context));
@@ -1710,7 +1731,7 @@
return true;
}
- for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ for (TaskContainer taskContainer : mTaskContainers) {
for (SystemShortcut s : getEnabledShortcuts(this,
taskContainer)) {
if (s.hasHandlerForAction(action)) {
@@ -1724,8 +1745,8 @@
}
@Nullable
- public RecentsView getRecentsView() {
- return (RecentsView) getParent();
+ public RecentsView<?, ?> getRecentsView() {
+ return (RecentsView<?, ?>) getParent();
}
RecentsPagedOrientationHandler getPagedOrientationHandler() {
@@ -1734,8 +1755,9 @@
private void notifyTaskLaunchFailed(String tag) {
String msg = "Failed to launch task";
- if (mTask != null) {
- msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
+ if (getFirstTask() != null) {
+ msg += " (task=" + getFirstTask().key.baseIntent + " userId="
+ + getFirstTask().key.userId + ")";
}
Log.w(tag, msg);
Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
@@ -1750,10 +1772,7 @@
progress = Utilities.boundToRange(progress, 0, 1);
mFullscreenProgress = progress;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826840) Add corner rounding to new TTV
- mTaskThumbnailViewDeprecated.getTaskOverlay().setFullscreenProgress(progress);
- }
+ mTaskThumbnailViewDeprecated.getTaskOverlay().setFullscreenProgress(progress);
RecentsView recentsView = mContainer.getOverviewPanel();
// Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
@@ -1767,10 +1786,7 @@
protected void updateSnapshotRadius() {
updateCurrentFullscreenParams();
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826840) Add corner rounding to new TTV
- mTaskThumbnailViewDeprecated.setFullscreenParams(mCurrentFullscreenParams);
- }
+ mTaskThumbnailViewDeprecated.setFullscreenParams(mCurrentFullscreenParams);
}
void updateCurrentFullscreenParams() {
@@ -1781,8 +1797,8 @@
if (getRecentsView() == null) {
return;
}
- fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
- getScaleX());
+ fullscreenParams.setProgress(
+ mFullscreenProgress, getRecentsView().getScaleX(), getScaleX());
}
/**
@@ -1976,7 +1992,10 @@
}
}
- public class TaskIdAttributeContainer {
+ /**
+ * Holder for all Task dependent information.
+ */
+ public class TaskContainer {
private final TaskThumbnailViewDeprecated mThumbnailView;
private final Task mTask;
private final TaskViewIcon mIconView;
@@ -1984,15 +2003,18 @@
private @SplitConfigurationOptions.StagePosition int mStagePosition;
@IdRes
private final int mA11yNodeId;
+ private final DigitalWellBeingToast mDigitalWellBeingToast;
- public TaskIdAttributeContainer(Task task, TaskThumbnailViewDeprecated thumbnailView,
- TaskViewIcon iconView, int stagePosition) {
+ public TaskContainer(Task task, TaskThumbnailViewDeprecated thumbnailView,
+ TaskViewIcon iconView, int stagePosition,
+ DigitalWellBeingToast digitalWellBeingToast) {
this.mTask = task;
this.mThumbnailView = thumbnailView;
this.mIconView = iconView;
this.mStagePosition = stagePosition;
this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
+ this.mDigitalWellBeingToast = digitalWellBeingToast;
}
public TaskThumbnailViewDeprecated getThumbnailView() {
@@ -2026,5 +2048,9 @@
public int getA11yNodeId() {
return mA11yNodeId;
}
+
+ public DigitalWellBeingToast getDigitalWellBeingToast() {
+ return mDigitalWellBeingToast;
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 7065075..2bcfa3f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -264,6 +264,120 @@
assertThat(animatorScheduler.delayedBlock).isNull()
}
+ @Test
+ fun animateToInitialState_inApp() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = true, isExpanding = false)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(barAnimator.isRunning()).isFalse()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(0)
+ assertThat(handle.translationY).isEqualTo(0)
+ assertThat(handle.alpha).isEqualTo(1)
+
+ verify(bubbleStashController).stashBubbleBarImmediate()
+ }
+
+ @Test
+ fun animateToInitialState_inApp_autoExpanding() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = true, isExpanding = true)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(barAnimator.isRunning()).isFalse()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ verify(bubbleStashController).showBubbleBarImmediate()
+ }
+
+ @Test
+ fun animateToInitialState_inHome() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ assertThat(barAnimator.isRunning()).isFalse()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+ verify(bubbleStashController).showBubbleBarImmediate()
+ }
+
private fun setUpBubbleBar() {
bubbleBarView = BubbleBarView(context)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -322,3 +436,4 @@
private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f
private const val HANDLE_TRANSLATION = -30f
private const val BAR_TRANSLATION_Y_FOR_TASKBAR = -50f
+private const val BAR_TRANSLATION_Y_FOR_HOTSEAT = -40f
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index e71192f..efd7bec 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -17,38 +17,63 @@
package com.android.quickstep.task.thumbnail
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.systemui.shared.recents.model.Task
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TaskThumbnailViewModelTest {
- private val systemUnderTest = TaskThumbnailViewModel()
+ private val recentsViewData = RecentsViewData()
+ private val taskViewData = TaskViewData()
+ private val systemUnderTest = TaskThumbnailViewModel(recentsViewData, taskViewData)
@Test
- fun initialStateIsUninitialized() {
- assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.Uninitialized)
+ fun initialStateIsUninitialized() = runTest {
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.Uninitialized)
}
@Test
- fun bindRunningTask_thenStateIs_LiveTile() {
+ fun bindRunningTask_thenStateIs_LiveTile() = runTest {
val taskThumbnail = TaskThumbnail(Task(), isRunning = true)
systemUnderTest.bind(taskThumbnail)
- assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.LiveTile)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.LiveTile)
}
@Test
- fun bindRunningTaskThenStoppedTask_thenStateIs_Uninitialized() {
+ fun setRecentsFullscreenProgress_thenProgressIsPassedThrough() = runTest {
+ recentsViewData.fullscreenProgress.value = 0.5f
+
+ assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.5f)
+
+ recentsViewData.fullscreenProgress.value = 0.6f
+
+ assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.6f)
+ }
+
+ @Test
+ fun setAncestorScales_thenScaleIsCalculated() = runTest {
+ recentsViewData.scale.value = 0.5f
+ taskViewData.scale.value = 0.6f
+
+ assertThat(systemUnderTest.inheritedScale.first()).isEqualTo(0.3f)
+ }
+
+ @Test
+ fun bindRunningTaskThenStoppedTask_thenStateIs_Uninitialized() = runTest {
// TODO(b/334825222): Change the expectation here when snapshot state is implemented
val task = Task()
val runningTask = TaskThumbnail(task, isRunning = true)
val stoppedTask = TaskThumbnail(task, isRunning = false)
systemUnderTest.bind(runningTask)
- assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.LiveTile)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.LiveTile)
systemUnderTest.bind(stoppedTask)
- assertThat(systemUnderTest.uiState.value).isEqualTo(TaskThumbnailUiState.Uninitialized)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.Uninitialized)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 0f9d96c..d59aafb 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -16,12 +16,12 @@
package com.android.quickstep
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
import android.content.ComponentName
import android.content.Intent
import android.platform.test.flag.junit.SetFlagsRule
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingViewHelper
import com.android.launcher3.logging.StatsLogManager
@@ -39,12 +39,12 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.mockito.quality.Strictness
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/** Test for DesktopSystemShortcut */
class DesktopSystemShortcutTest {
@@ -64,15 +64,18 @@
private lateinit var mockitoSession: StaticMockitoSession
@Before
- fun setUp(){
- mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java).startMocking()
+ fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
}
@After
- fun tearDown(){
+ fun tearDown() {
mockitoSession.finishMocking()
}
@@ -85,11 +88,12 @@
isDockable = true
}
val taskContainer =
- taskView.TaskIdAttributeContainer(
+ taskView.TaskContainer(
task,
null,
null,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
)
val shortcuts = factory.getShortcuts(launcher, taskContainer)
@@ -106,11 +110,12 @@
isDockable = true
}
val taskContainer =
- taskView.TaskIdAttributeContainer(
+ taskView.TaskContainer(
task,
null,
null,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
)
val shortcuts = factory.getShortcuts(launcher, taskContainer)
@@ -128,11 +133,12 @@
isDockable = true
}
val taskContainer =
- taskView.TaskIdAttributeContainer(
+ taskView.TaskContainer(
task,
null,
null,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
)
val shortcuts = factory.getShortcuts(launcher, taskContainer)
@@ -148,11 +154,12 @@
isDockable = false
}
val taskContainer =
- taskView.TaskIdAttributeContainer(
+ taskView.TaskContainer(
task,
null,
null,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
)
val shortcuts = factory.getShortcuts(launcher, taskContainer)
@@ -168,11 +175,12 @@
isDockable = true
}
val taskContainer =
- taskView.TaskIdAttributeContainer(
+ taskView.TaskContainer(
task,
null,
null,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
)
whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
index 4aa7cb0..37ab131 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
@@ -86,8 +86,8 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- assertTrue("Latest task is not Calculator",
- CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
+ assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(
+ task.getFirstTask().getTopComponent().getPackageName()));
return task.getDigitalWellBeingToast();
});
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index efe773b..50f74c2 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.launcher3.allapps.PrivateProfileManager;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.PrivateSpaceContainer;
@@ -34,6 +35,7 @@
import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -164,6 +166,60 @@
}
}
+ @Test
+ @ScreenRecordRule.ScreenRecord // b/334946529
+ @Ignore("b/339179262")
+ public void testPrivateSpaceLockingBehaviour() throws IOException {
+ // Scroll to the bottom of All Apps
+ executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+
+ // Disable Private Space
+ togglePrivateSpace(PrivateProfileManager.STATE_DISABLED, homeAllApps);
+
+ homeAllApps.freeze();
+ try {
+ // Verify Locked View elements are present.
+ homeAllApps.getPrivateSpaceLockedView();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+
+ // Enable Private Space
+ togglePrivateSpace(PrivateProfileManager.STATE_ENABLED, homeAllApps);
+
+ homeAllApps.freeze();
+ try {
+ // Verify UnLocked View elements are present.
+ homeAllApps.getPrivateSpaceUnlockedView();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ }
+
+ private void togglePrivateSpace(int state, HomeAllApps homeAllApps) {
+ homeAllApps.freeze();
+ try {
+ // Try Toggling Private Space
+ homeAllApps.togglePrivateSpace();
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
+ PrivateProfileManager manager = getFromLauncher(l -> l.getAppsView()
+ .getPrivateProfileManager());
+ waitForLauncherCondition("Private profile toggle to state: " + state + " failed",
+ launcher -> {
+ manager.reset();
+ return manager.getCurrentState() == state;
+ },
+ LauncherInstrumentation.WAIT_TIME_MS);
+ // Wait for Launcher UI to be updated with Private Space Items.
+ waitForLauncherUIUpdate();
+ }
+
private void waitForPrivateSpaceSetup() {
waitForLauncherCondition("Private Profile not setup",
launcher -> launcher.getAppsView().hasPrivateProfile(),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index 2a54057..bfd7bdb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -130,7 +130,7 @@
.tapMenu()
.hasMenuItem("Save app pair"));
} else {
- overview.getOverviewActions().assertHasAction("Save app pair");
+ overview.getOverviewGroupActions().assertHasAction("Save app pair");
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index de98703..f29df61 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -35,7 +35,7 @@
import com.android.quickstep.views.IconView
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskView.TaskContainer
import com.android.systemui.shared.recents.model.Task
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -67,7 +67,7 @@
private val mockGroupedTaskView: GroupedTaskView = mock()
private val mockTask: Task = mock()
private val mockTaskKey: Task.TaskKey = mock()
- private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
+ private val mockTaskContainer: TaskContainer = mock()
// AppPairIcon
private val mockAppPairIcon: AppPairIcon = mock()
private val mockContextThemeWrapper: ContextThemeWrapper = mock()
@@ -83,14 +83,15 @@
private val transitionInfo: TransitionInfo = mock()
private val transaction: Transaction = mock()
- lateinit var splitAnimationController: SplitAnimationController
+ private lateinit var splitAnimationController: SplitAnimationController
@Before
fun setup() {
- whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
+ whenever(mockTaskContainer.thumbnailView).thenReturn(mockThumbnailView)
whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
- whenever(mockTaskView.iconView).thenReturn(mockIconView)
+ whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
+ whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
@@ -177,14 +178,13 @@
// Remove icon view from GroupedTaskView
whenever(mockIconView.drawable).thenReturn(null)
- whenever(mockTaskIdAttributeContainer.task).thenReturn(mockTask)
- whenever(mockTaskIdAttributeContainer.iconView).thenReturn(mockIconView)
- whenever(mockTaskIdAttributeContainer.thumbnailView).thenReturn(mockThumbnailView)
+ whenever(mockTaskContainer.task).thenReturn(mockTask)
+ whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
+ whenever(mockTaskContainer.thumbnailView).thenReturn(mockThumbnailView)
whenever(mockTask.getKey()).thenReturn(mockTaskKey)
whenever(mockTaskKey.getId()).thenReturn(taskId)
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
- whenever(mockGroupedTaskView.taskIdAttributeContainers)
- .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+ whenever(mockGroupedTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
splitAnimationController.getFirstAnimInitViews(
{ mockGroupedTaskView },
@@ -277,9 +277,7 @@
doNothing()
.whenever(spySplitAnimationController)
.composeIconSplitLaunchAnimator(any(), any(), any(), any())
- doReturn(-1)
- .whenever(spySplitAnimationController)
- .hasChangesForBothAppPairs(any(), any())
+ doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -305,41 +303,10 @@
val spySplitAnimationController = spy(splitAnimationController)
whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
- .whenever(spySplitAnimationController)
- .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any())
- doReturn(0)
- .whenever(spySplitAnimationController)
- .hasChangesForBothAppPairs(any(), any())
-
- spySplitAnimationController.playSplitLaunchAnimation(
- null /* launchingTaskView */,
- mockAppPairIcon,
- taskId,
- taskId2,
- null /* apps */,
- null /* wallpapers */,
- null /* nonApps */,
- stateManager,
- depthController,
- transitionInfo,
- transaction,
- {} /* finishCallback */
- )
-
- verify(spySplitAnimationController)
- .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0))
- }
-
- @Test
- fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() {
- val spySplitAnimationController = spy(splitAnimationController)
- whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
- doNothing()
.whenever(spySplitAnimationController)
- .composeScaleUpLaunchAnimation(any(), any(), any(), any())
- doReturn(-1)
- .whenever(spySplitAnimationController)
- .hasChangesForBothAppPairs(any(), any())
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any())
+ doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
+
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
mockAppPairIcon,
@@ -355,8 +322,35 @@
{} /* finishCallback */
)
- verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
- eq(WINDOWING_MODE_MULTI_WINDOW))
+ verify(spySplitAnimationController)
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0))
+ }
+
+ @Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_MULTI_WINDOW))
}
@Test
@@ -364,28 +358,26 @@
val spySplitAnimationController = spy(splitAnimationController)
whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
doNothing()
- .whenever(spySplitAnimationController)
- .composeScaleUpLaunchAnimation(any(), any(), any(), any())
- doReturn(0)
- .whenever(spySplitAnimationController)
- .hasChangesForBothAppPairs(any(), any())
+ .whenever(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(0).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
- null /* launchingTaskView */,
- mockAppPairIcon,
- taskId,
- taskId2,
- null /* apps */,
- null /* wallpapers */,
- null /* nonApps */,
- stateManager,
- depthController,
- transitionInfo,
- transaction,
- {} /* finishCallback */
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
)
- verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
- eq(WINDOWING_MODE_FULLSCREEN))
+ verify(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), eq(WINDOWING_MODE_FULLSCREEN))
}
@Test
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 462bb52..b6412db 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -107,6 +107,7 @@
android:drawablePadding="@dimen/widget_cell_add_button_drawable_padding"
android:drawableTint="?attr/widgetPickerAddButtonTextColor"
android:maxLines="1"
+ style="@style/Button.Rounded.Colored"
android:background="@drawable/widget_cell_add_button_background" />
</FrameLayout>
</merge>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 28fb119..8b69318 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj na početni ekran"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Dodali ste vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> na početni ekran"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Predlozi"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Neophodne aplikacije"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Osnovno"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Novosti i časopisi"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona za opuštanje"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zabava"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index b05002d..736eaab 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Přidat na plochu"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> byl přidán na plochu"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Návrhy"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Základní"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Nejdůležitější aplikace"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Zprávy a časopisy"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Vaše klidová zóna"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zábava"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4aaa44b..623b183 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -48,7 +48,7 @@
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noticias y revistas"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Zona de descanso"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimiento"</string>
- <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociales"</string>
<string name="fitness_widget_recommendation_category_label" msgid="2756483898236585324">"Salud y bienestar"</string>
<string name="weather_widget_recommendation_category_label" msgid="3059715991930798039">"Clima"</string>
<string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugerencias para ti"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index c91e510..4df953a 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -48,7 +48,7 @@
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noticias y revistas"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Tu zona de descanso"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Entretenimiento"</string>
- <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Social"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Redes sociales"</string>
<string name="fitness_widget_recommendation_category_label" msgid="2756483898236585324">"Salud y actividad física"</string>
<string name="weather_widget_recommendation_category_label" msgid="3059715991930798039">"El tiempo"</string>
<string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sugerencias para ti"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4f6f121..4c72f00 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Ajouter à l\'écran d\'accueil"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> ajouté à l\'écran d\'accueil"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggestions"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Les bases"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Indispensables"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Actualités et magazines"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Votre espace détente"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Divertissement"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b6af6b3..da6a711 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj do ekranu głównego"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> został dodany do ekranu głównego"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestie"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Niezbędne"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Najbardziej przydatne"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Wiadomości i czasopisma"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Strefa relaksu"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Rozrywka"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index bb82b67..9eaae37 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Додај на почетни екран"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Додали сте виџет <xliff:g id="WIDGET_NAME">%1$s</xliff:g> на почетни екран"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Предлози"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Неопходне апликације"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основно"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и часописи"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"Зона за опуштање"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Забава"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 2975e97..a18aab6 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -44,7 +44,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"添加到主屏幕"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"必备"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"必备之选"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"新闻与杂志"</string>
<string name="social_and_entertainment_widget_recommendation_category_label" msgid="2923840997302308191">"您的休闲区"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"娱乐"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 648a50c..a808a3f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -223,4 +223,11 @@
<string-array name="skip_private_profile_shortcut_packages" translatable="false">
<item>com.android.settings</item>
</string-array>
+
+ <!-- Legacy list of components supporting multiple instances.
+ DO NOT ADD TO THIS LIST. Apps should use the PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
+ property to declare multi-instance support in V+. This resource should match the resource
+ of the same name in SystemUI. -->
+ <string-array name="config_appsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c971223..c3d4273 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -485,7 +485,7 @@
<!-- Description for Private Space Transition button -->
<string name="ps_container_transition">Private Space Transitioning</string>
<!-- Title for Private Space install app icon -->
- <string name="ps_add_button_label">Install apps</string>
+ <string name="ps_add_button_label">Install</string>
<!-- Content description for install app icon -->
<string name="ps_add_button_content_description">Install apps to Private Space</string>
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
index 08fb47b..8494d11 100644
--- a/src/com/android/launcher3/DevicePaddings.java
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -47,7 +47,7 @@
private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";
- private static final String TAG = DevicePaddings.class.getSimpleName();
+ private static final String TAG = "DevicePaddings";
private static final boolean DEBUG = false;
ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 009d709..b89d05e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -96,7 +96,9 @@
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.testing.shared.TestProtocol.CLOCK_ICON_DRAWABLE_LEAKING;
import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
@@ -421,6 +423,7 @@
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onCreate: instance=" + this);
mStartupLatencyLogger = createStartupLatencyLogger(
sIsNewProcess
? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
@@ -1079,6 +1082,7 @@
@Override
protected void onStart() {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onStart: instance=" + this);
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
super.onStart();
if (!mDeferOverlayCallbacks) {
@@ -1092,6 +1096,7 @@
@Override
@CallSuper
protected void onDeferredResumed() {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onDeferredResumed: instance=" + this);
logStopAndResume(true /* isResume */);
// Process any items that were added while Launcher was away.
@@ -1279,6 +1284,7 @@
@Override
protected void onResume() {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onResume: instance=" + this);
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
@@ -1294,6 +1300,7 @@
@Override
protected void onPause() {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onPause: instance=" + this);
// Ensure that items added to Launcher are queued until Launcher returns
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
@@ -1776,6 +1783,7 @@
@Override
public void onDestroy() {
+ testLogD(CLOCK_ICON_DRAWABLE_LEAKING, "onDestroy: instance=" + this);
super.onDestroy();
ACTIVITY_TRACKER.onActivityDestroyed(this);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 03de334..a4ae1c8 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -52,6 +52,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
@@ -181,7 +182,7 @@
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
- iconCacheFileName != null);
+ PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
}
@@ -206,7 +207,7 @@
}
private void refreshAndReloadLauncher() {
- LauncherIcons.clearPool();
+ LauncherIcons.clearPool(mContext);
mIconCache.updateIconParams(
mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
mModel.forceReload();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index be98589..e3da389 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -98,6 +98,8 @@
@NonNull
private final LauncherAppState mApp;
@NonNull
+ private final PackageManagerHelper mPmHelper;
+ @NonNull
private final ModelDbController mModelDbController;
@NonNull
private final Object mLock = new Object();
@@ -152,12 +154,13 @@
LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
@NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
- final boolean isPrimaryInstance) {
+ @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
mApp = app;
+ mPmHelper = pmHelper;
mModelDbController = new ModelDbController(context);
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
- isPrimaryInstance);
+ mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
+ mBgDataModel, isPrimaryInstance);
}
@NonNull
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index cc9f08e..365fbd3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -788,7 +788,7 @@
if (mScroller.isFinished() && pageScrollChanged) {
// TODO(b/246283207): Remove logging once root cause of flake detected.
if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) {
- Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
+ Log.d("b/246283207", TAG + "#onLayout() -> "
+ "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+ getNextPage() + ", getScrollForPage(getNextPage()): "
+ getScrollForPage(getNextPage()));
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b9a62e2..4a26a18 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -70,6 +70,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.ChecksSdkIntAtLeast;
@@ -104,6 +105,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -132,6 +134,10 @@
@ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U")
public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE;
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V")
+ public static final boolean ATLEAST_V = Build.VERSION.SDK_INT
+ >= VERSION_CODES.VANILLA_ICE_CREAM;
+
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
@@ -835,4 +841,27 @@
// No-Op
}
}
+
+ /**
+ * Does a depth-first search through the View hierarchy starting at root, to find a view that
+ * matches the predicate. Returns null if no View was found. View has a findViewByPredicate
+ * member function but it is currently a @hide API.
+ */
+ @Nullable
+ public static <T extends View> T findViewByPredicate(@NonNull View root,
+ @NonNull Predicate<View> predicate) {
+ if (predicate.test(root)) {
+ return (T) root;
+ }
+ if (root instanceof ViewGroup parent) {
+ int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View view = findViewByPredicate(parent.getChildAt(i), predicate);
+ if (view != null) {
+ return (T) view;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index ae0e80c..53f9cfc 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -20,7 +20,6 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PRIVATESPACE;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
@@ -28,8 +27,12 @@
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_LOCK_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
@@ -635,8 +638,6 @@
return;
}
ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
- ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
- TextView lockText = lockButton.findViewById(R.id.lock_text);
if (settingsAndLockGroup.getLayoutTransition() == null) {
// Set a new transition if the current ViewGroup does not already contain one as each
// transition should only happen once when applied.
@@ -646,21 +647,30 @@
LayoutTransition.CHANGING,
expand ? SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY : NO_DELAY);
PropertySetter headerSetter = new AnimatedPropertySetter();
- ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
- updateSettingsGearAlpha(settingsButton, expand, headerSetter);
- updateLockTextAlpha(lockText, expand, headerSetter);
+ headerSetter.add(updateSettingsGearAlpha(expand));
+ headerSetter.add(updateLockTextAlpha(expand));
AnimatorSet animatorSet = headerSetter.buildAnim();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ expand
+ ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN
+ : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN,
+ mAllApps.getActiveRecyclerView());
// Animate the collapsing of the text at the same time while updating lock button.
- lockText.setVisibility(expand ? VISIBLE : GONE);
+ mPSHeader.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
setAnimationRunning(true);
}
});
animatorSet.addListener(forEndCallback(() -> {
setAnimationRunning(false);
getMainRecyclerView().setChildAttachedConsumer(child -> child.setAlpha(1));
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ expand
+ ? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END
+ : LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END,
+ mAllApps.getActiveRecyclerView());
if (!expand) {
// Call onAppsUpdated() because it may be canceled when this animation occurs.
mAllApps.getPersonalAppList().onAppsUpdated();
@@ -728,19 +738,44 @@
}
/** Change the settings gear alpha when expanded or collapsed. */
- private void updateSettingsGearAlpha(ImageButton settingsButton, boolean expand,
- PropertySetter setter) {
- float toAlpha = expand ? 1 : 0;
- setter.setFloat(settingsButton, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
- .setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(expand ?
- SETTINGS_OPACITY_DELAY : NO_DELAY);
+ private ValueAnimator updateSettingsGearAlpha(boolean expand) {
+ if (mPSHeader == null) {
+ return new ValueAnimator();
+ }
+ float from = expand ? 0 : 1;
+ float to = expand ? 1 : 0;
+ ValueAnimator settingsAlphaAnim = ObjectAnimator.ofFloat(from, to);
+ settingsAlphaAnim.setDuration(SETTINGS_OPACITY_DURATION);
+ settingsAlphaAnim.setStartDelay(expand ? SETTINGS_OPACITY_DELAY : NO_DELAY);
+ settingsAlphaAnim.setInterpolator(Interpolators.LINEAR);
+ settingsAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mPSHeader.findViewById(R.id.ps_settings_button)
+ .setAlpha((float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return settingsAlphaAnim;
}
- private void updateLockTextAlpha(TextView textView, boolean expand, PropertySetter setter) {
- float toAlpha = expand ? 1 : 0;
- setter.setFloat(textView, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
- .setDuration(expand ? TEXT_UNLOCK_OPACITY_DURATION : TEXT_LOCK_OPACITY_DURATION)
- .setStartDelay(expand ? LOCK_TEXT_OPACITY_DELAY : NO_DELAY);
+ private ValueAnimator updateLockTextAlpha(boolean expand) {
+ if (mPSHeader == null) {
+ return new ValueAnimator();
+ }
+ float from = expand ? 0 : 1;
+ float to = expand ? 1 : 0;
+ ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
+ alphaAnim.setDuration(expand ? TEXT_UNLOCK_OPACITY_DURATION : TEXT_LOCK_OPACITY_DURATION);
+ alphaAnim.setStartDelay(expand ? LOCK_TEXT_OPACITY_DELAY : NO_DELAY);
+ alphaAnim.setInterpolator(Interpolators.LINEAR);
+ alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mPSHeader.findViewById(R.id.lock_text).setAlpha(
+ (float) valueAnimator.getAnimatedValue());
+ }
+ });
+ return alphaAnim;
}
void expandPrivateSpace() {
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index 6a1f37a..3351ee3 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -52,11 +52,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface UserProfileState { }
+ protected final StatsLogManager mStatsLogManager;
@UserProfileState
private int mCurrentState;
private final UserManager mUserManager;
- private final StatsLogManager mStatsLogManager;
private final UserCache mUserCache;
protected UserProfileManager(UserManager userManager,
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 1f388c2..ae8f1d5 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -76,7 +76,6 @@
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WidgetItem;
@@ -107,7 +106,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -126,44 +124,12 @@
*/
public static class PreviewContext extends SandboxContext {
- private final InvariantDeviceProfile mIdp;
- private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
- new ConcurrentLinkedQueue<>();
-
public PreviewContext(Context base, InvariantDeviceProfile idp) {
super(base);
- mIdp = idp;
putObject(InvariantDeviceProfile.INSTANCE, idp);
putObject(LauncherAppState.INSTANCE,
new LauncherAppState(this, null /* iconCacheFileName */));
}
-
- /**
- * Creates a new LauncherIcons for the preview, skipping the global pool
- */
- public LauncherIcons newLauncherIcons(Context context) {
- LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
- if (launcherIconsForPreview != null) {
- return launcherIconsForPreview;
- }
- return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
- -1 /* poolId */);
- }
-
- private final class LauncherIconsForPreview extends LauncherIcons {
-
- private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
- int poolId) {
- super(context, fillResIconDpi, iconBitmapSize, poolId);
- }
-
- @Override
- public void recycle() {
- // Clear any temporary state variables
- clear();
- mIconPool.offer(this);
- }
- }
}
private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 7331c6f..e90a1e0 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -25,79 +25,53 @@
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.UserIconInfo;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
* that are threadsafe.
*/
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
- private static final Object sPoolSync = new Object();
- private static LauncherIcons sPool;
- private static int sPoolId = 0;
+ private static final MainThreadInitializedObject<Pool> POOL =
+ new MainThreadInitializedObject<>(Pool::new);
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context) {
- if (context instanceof LauncherPreviewRenderer.PreviewContext) {
- return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context);
- }
-
- int poolId;
- synchronized (sPoolSync) {
- if (sPool != null) {
- LauncherIcons m = sPool;
- sPool = m.next;
- m.next = null;
- return m;
- }
- poolId = sPoolId;
- }
-
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
- return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
+ return POOL.get(context).obtain();
}
- public static void clearPool() {
- synchronized (sPoolSync) {
- sPool = null;
- sPoolId++;
- }
+ public static void clearPool(Context context) {
+ POOL.get(context).close();
}
- private final int mPoolId;
-
- private LauncherIcons next;
+ private final ConcurrentLinkedQueue<LauncherIcons> mPool;
private MonochromeIconFactory mMonochromeIconFactory;
- protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
+ protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
+ ConcurrentLinkedQueue<LauncherIcons> pool) {
super(context, fillResIconDpi, iconBitmapSize,
IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
mMonoIconEnabled = Themes.isThemedIconEnabled(context);
- mPoolId = poolId;
+ mPool = pool;
}
/**
* Recycles a LauncherIcons that may be in-use.
*/
public void recycle() {
- synchronized (sPoolSync) {
- if (sPoolId != mPoolId) {
- return;
- }
- // Clear any temporary state variables
- clear();
-
- next = sPool;
- sPool = this;
- }
+ clear();
+ mPool.add(this);
}
@Override
@@ -122,4 +96,33 @@
public void close() {
recycle();
}
+
+ private static class Pool implements SafeCloseable {
+
+ private final Context mContext;
+
+ @NonNull
+ private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
+
+ private Pool(Context context) {
+ mContext = context;
+ }
+
+ public LauncherIcons obtain() {
+ ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
+ LauncherIcons m = pool.poll();
+
+ if (m == null) {
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+ return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
+ } else {
+ return m;
+ }
+ }
+
+ @Override
+ public void close() {
+ mPool = new ConcurrentLinkedQueue<>();
+ }
+ }
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2f3c2b6..25eeacb 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -769,7 +769,19 @@
LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT(1672),
@UiEvent(doc = "Number of preinstalled Private profile apps, shown under separator line")
- LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT(1673)
+ LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT(1673),
+
+ @UiEvent(doc = "Private space lock animation started")
+ LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN(1725),
+
+ @UiEvent(doc = "Private space lock animation finished")
+ LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END(1726),
+
+ @UiEvent(doc = "Private space unlock animation started")
+ LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN(1727),
+
+ @UiEvent(doc = "Private space unlock animation finished")
+ LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END(1728)
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 39c1243..bcd6ad2 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -301,6 +301,7 @@
Context context, String packageName, UserHandle user) {
final ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(context);
final UserCache userCache = UserCache.getInstance(context);
+ final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
.getActivityList(packageName, user);
if (matches.size() > 0) {
@@ -330,7 +331,7 @@
applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
applicationInfo.intent = launchIntent;
AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
- userCache.getUserInfo(user), apiWrapper);
+ userCache.getUserInfo(user), apiWrapper, pmHelper);
mDataChanged = true;
}
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 0875974..84130c7 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -57,6 +57,7 @@
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.UserIconInfo;
import java.net.URISyntaxException;
@@ -73,6 +74,7 @@
private final LauncherAppState mApp;
private final Context mContext;
+ private final PackageManagerHelper mPmHelper;
private final IconCache mIconCache;
private final InvariantDeviceProfile mIDP;
private final @Nullable LauncherRestoreEventLogger mRestoreEventLogger;
@@ -114,6 +116,7 @@
public int restoreFlag;
public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState,
+ PackageManagerHelper pmHelper,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
super(cursor);
@@ -121,6 +124,7 @@
allUsers = userManagerState.allUsers;
mContext = app.getContext();
mIconCache = app.getIconCache();
+ mPmHelper = pmHelper;
mIDP = app.getInvariantDeviceProfile();
mRestoreEventLogger = restoreEventLogger;
@@ -368,7 +372,7 @@
if (mActivityInfo != null) {
AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
- ApiWrapper.INSTANCE.get(mContext));
+ ApiWrapper.INSTANCE.get(mContext), mPmHelper);
}
// from the db
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 876bed4..0d40a24 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -435,7 +435,8 @@
mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
dbController.query(TABLE_NAME, null, selection, null, null),
- mApp, mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null);
+ mApp, mUserManagerState, mPmHelper,
+ mIsRestoreFromBackup ? restoreEventLogger : null);
final Bundle extras = c.getExtras();
mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
try {
@@ -697,7 +698,7 @@
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user),
- ApiWrapper.INSTANCE.get(mApp.getContext()), quietMode);
+ ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode);
if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
// For archived apps, include progress info in case there is a pending
// install session post restart of device.
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 8360b14..2264d35 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.ResourceBasedOverride;
import java.io.FileDescriptor;
@@ -41,15 +42,16 @@
* Creates and initializes a new instance of the delegate
*/
public static ModelDelegate newInstance(
- Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
- boolean isPrimaryInstance) {
+ Context context, LauncherAppState app, PackageManagerHelper pmHelper,
+ AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
- delegate.init(app, appsList, dataModel, isPrimaryInstance);
+ delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
return delegate;
}
protected final Context mContext;
+ protected PackageManagerHelper mPmHelper;
protected LauncherAppState mApp;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
@@ -62,9 +64,10 @@
/**
* Initializes the object with the given params.
*/
- private void init(LauncherAppState app, AllAppsList appsList,
+ private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
BgDataModel dataModel, boolean isPrimaryInstance) {
this.mApp = app;
+ this.mPmHelper = pmHelper;
this.mAppsList = appsList;
this.mDataModel = dataModel;
this.mIsPrimaryInstance = isPrimaryInstance;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 802faae..6275ed0 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -126,8 +126,8 @@
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
appsList.removePackage(packages[i], mUser);
}
- activitiesLists.put(
- packages[i], appsList.addPackage(context, packages[i], mUser));
+ activitiesLists.put(packages[i],
+ appsList.addPackage(context, packages[i], mUser));
}
flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
@@ -138,8 +138,8 @@
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
- activitiesLists.put(
- packages[i], appsList.updatePackage(context, packages[i], mUser));
+ activitiesLists.put(packages[i],
+ appsList.updatePackage(context, packages[i], mUser));
}
}
// Since package was just updated, the target must be available now.
@@ -269,6 +269,8 @@
if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
+ // TODO: See if we can migrate this to
+ // AppInfo#updateRuntimeFlagsForActivityTarget
si.setProgressLevel(
activities == null || activities.isEmpty()
? 100
@@ -399,7 +401,8 @@
return false;
}
// Try to find the best match activity.
- Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
+ Intent intent = PackageManagerHelper.INSTANCE.get(context)
+ .getAppLaunchIntent(packageName, mUser);
if (intent != null) {
si.intent = intent;
si.status = WorkspaceItemInfo.DEFAULT;
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index cea4380..90e47d6 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -336,7 +336,8 @@
info,
activityInfo,
userCache.getUserInfo(c.user),
- ApiWrapper.INSTANCE[app.context]
+ ApiWrapper.INSTANCE[app.context],
+ pmHelper
)
}
if (
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 18aa6e7..a4281f8 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -90,12 +90,12 @@
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
this(info, UserCache.INSTANCE.get(context).getUserInfo(user),
- ApiWrapper.INSTANCE.get(context),
+ ApiWrapper.INSTANCE.get(context), PackageManagerHelper.INSTANCE.get(context),
context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo,
- ApiWrapper apiWrapper, boolean quietModeEnabled) {
+ ApiWrapper apiWrapper, PackageManagerHelper pmHelper, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = CONTAINER_ALL_APPS;
this.user = userIconInfo.user;
@@ -105,7 +105,7 @@
runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
}
uid = info.getApplicationInfo().uid;
- updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper);
+ updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper, pmHelper);
}
public AppInfo(AppInfo info) {
@@ -184,7 +184,7 @@
*/
public static boolean updateRuntimeFlagsForActivityTarget(
ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo,
- ApiWrapper apiWrapper) {
+ ApiWrapper apiWrapper, PackageManagerHelper pmHelper) {
final int oldProgressLevel = info.getProgressLevel();
final int oldRuntimeStatusFlags = info.runtimeStatusFlags;
ApplicationInfo appInfo = lai.getApplicationInfo();
@@ -216,6 +216,8 @@
PackageManagerHelper.getLoadingProgress(lai),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
+ info.setSupportsMultiInstance(
+ pmHelper.supportsMultiInstance(lai.getComponentName()));
return (oldProgressLevel != info.getProgressLevel())
|| (oldRuntimeStatusFlags != info.runtimeStatusFlags);
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 72e85c7..b82d0a0 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -73,6 +73,7 @@
* Represents an item in the launcher.
*/
public class ItemInfo {
+ private static final String TAG = "ItemInfo";
public static final boolean DEBUG = false;
public static final int NO_ID = -1;
@@ -285,7 +286,7 @@
@Override
@NonNull
public final String toString() {
- return getClass().getSimpleName() + "(" + dumpProperties() + ")";
+ return TAG + "(" + dumpProperties() + ")";
}
@NonNull
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index d4c25cb..6ac44ff 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -127,6 +127,11 @@
public static final int FLAG_NOT_RESIZEABLE = 1 << 15;
/**
+ * Flag indicating whether the package related to the item & user supports multiple instances.
+ */
+ public static final int FLAG_SUPPORTS_MULTI_INSTANCE = 1 << 16;
+
+ /**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
*/
@@ -252,6 +257,24 @@
}
/**
+ * Sets whether this app info supports multi-instance.
+ */
+ protected void setSupportsMultiInstance(boolean supportsMultiInstance) {
+ if (supportsMultiInstance) {
+ runtimeStatusFlags |= FLAG_SUPPORTS_MULTI_INSTANCE;
+ } else {
+ runtimeStatusFlags &= ~FLAG_SUPPORTS_MULTI_INSTANCE;
+ }
+ }
+
+ /**
+ * Returns whether this app info supports multi-instance.
+ */
+ public boolean supportsMultiInstance() {
+ return (runtimeStatusFlags & FLAG_SUPPORTS_MULTI_INSTANCE) != 0;
+ }
+
+ /**
* Sets whether this app info is non-resizeable.
*/
public void setNonResizeable(boolean nonResizeable) {
@@ -301,4 +324,11 @@
drawable.setIsDisabled(isDisabled());
return drawable;
}
+
+ @Override
+ protected String dumpProperties() {
+ return super.dumpProperties()
+ + " supportsMultiInstance=" + supportsMultiInstance()
+ + " nonResizeable=" + isNonResizeable();
+ }
}
diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java
index 1797c1f..23d3b61 100644
--- a/src/com/android/launcher3/pm/PackageInstallInfo.java
+++ b/src/com/android/launcher3/pm/PackageInstallInfo.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
public final class PackageInstallInfo {
+ private static final String TAG = "PackageInstallInfo";
public static final int STATUS_INSTALLED = 0;
public static final int STATUS_INSTALLING = 1;
@@ -61,7 +62,7 @@
@Override
public String toString() {
- return getClass().getSimpleName() + "(" + dumpProperties() + ")";
+ return TAG + "(" + dumpProperties() + ")";
}
private String dumpProperties() {
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index b992a92..3ae643e 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -44,6 +44,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.PackageManagerHelper;
/**
* A set of utility methods for Launcher DB used for DB updates and migration.
@@ -107,9 +108,11 @@
Cursor c = db.query(
Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null);
UserManagerState ums = new UserManagerState();
+ PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
ums.init(UserCache.INSTANCE.get(context),
context.getSystemService(UserManager.class));
- LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, null);
+ LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper,
+ null);
IntSet deletedShortcuts = new IntSet();
while (lc.moveToNext()) {
diff --git a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index 7502a43..2c3035f 100644
--- a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
@@ -135,7 +135,7 @@
hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG #isValid - $message - $this")
}
companion object {
diff --git a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
index a4b25e5..ef9b7df 100644
--- a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
@@ -31,7 +31,7 @@
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) {
- "${this::class.simpleName} is invalid, only heightSpecs are allowed - " +
+ "$LOG_TAG is invalid, only heightSpecs are allowed - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -65,6 +65,7 @@
}
companion object {
+ private const val LOG_TAG = "ResponsiveCellSpecsProvider"
@JvmStatic
fun create(resourceHelper: ResourceHelper): ResponsiveCellSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
@@ -137,11 +138,11 @@
}
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
companion object {
- private const val LOG_TAG = "CellSpec"
+ const val LOG_TAG = "CellSpec"
}
}
@@ -182,6 +183,7 @@
)
companion object {
+ private const val LOG_TAG = "CalculatedCellSpec"
private fun getCalculatedValue(
availableSpace: Int,
spec: SizeSpec,
@@ -191,10 +193,10 @@
}
override fun toString(): String {
- return "${this::class.simpleName}(" +
+ return "$LOG_TAG(" +
"availableSpace=$availableSpace, iconSize=$iconSize, " +
"iconTextSize=$iconTextSize, iconDrawablePadding=$iconDrawablePadding, " +
- "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ "${CellSpec.LOG_TAG}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpec.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
index 65e0b32..e69324d 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpec.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -154,7 +154,7 @@
}
private fun logError(message: String) {
- Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+ Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
enum class DimensionType {
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
index 67eaac0..654608d 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
@@ -40,7 +40,7 @@
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) {
- "${this::class.simpleName} is incomplete - " +
+ "$LOG_TAG is incomplete - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -124,6 +124,7 @@
}
companion object {
+ private const val LOG_TAG = "ResponsiveSpecsProvider"
@JvmStatic
fun create(
resourceHelper: ResourceHelper,
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 0ed6ea0..d925629 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -84,7 +84,7 @@
*/
public class ItemClickHandler {
- private static final String TAG = ItemClickHandler.class.getSimpleName();
+ private static final String TAG = "ItemClickHandler";
/**
* Instance used for click handling on items
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 3684f56..f7c4df4 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -17,6 +17,7 @@
package com.android.launcher3.util;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -73,10 +74,14 @@
@NonNull
private final LauncherApps mLauncherApps;
+ private final String[] mLegacyMultiInstanceSupportedApps;
+
public PackageManagerHelper(@NonNull final Context context) {
mContext = context;
mPm = context.getPackageManager();
mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
+ mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
+ R.array.config_appsSupportMultiInstancesSplit);
}
@Override
@@ -159,11 +164,23 @@
}
}
+ /**
+ * Returns the preferred launch activity intent for a given package.
+ */
@Nullable
public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) {
+ LauncherActivityInfo info = getAppLaunchInfo(pkg, user);
+ return info != null ? AppInfo.makeLaunchIntent(info) : null;
+ }
+
+ /**
+ * Returns the preferred launch activity for a given package.
+ */
+ @Nullable
+ public LauncherActivityInfo getAppLaunchInfo(@Nullable final String pkg,
+ @NonNull final UserHandle user) {
List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
- return activities.isEmpty() ? null :
- AppInfo.makeLaunchIntent(activities.get(0));
+ return activities.isEmpty() ? null : activities.get(0);
}
/**
@@ -285,4 +302,47 @@
return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
Flags.enableSupportForArchiving() && info.isArchived);
}
+
+ /**
+ * Returns whether the given component or its application has the multi-instance property set.
+ */
+ public boolean supportsMultiInstance(@NonNull ComponentName component) {
+ // Check the legacy hardcoded allowlist first
+ for (String pkg : mLegacyMultiInstanceSupportedApps) {
+ if (pkg.equals(component.getPackageName())) {
+ return true;
+ }
+ }
+
+ // Check app multi-instance properties after V
+ if (!Utilities.ATLEAST_V) {
+ return false;
+ }
+
+ try {
+ // Check if the component has the multi-instance property
+ return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e1) {
+ try {
+ // Check if the application has the multi-instance property
+ return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,
+ component.getPackageName())
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e2) {
+ // Fall through
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether two apps should be considered the same for multi-instance purposes, which
+ * requires additional checks to ensure they can be started as multiple instances.
+ */
+ public static boolean isSameAppForMultiInstance(@NonNull ItemInfo app1,
+ @NonNull ItemInfo app2) {
+ return app1.getTargetPackage().equals(app2.getTargetPackage())
+ && app1.user.equals(app2.user);
+ }
}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index 2f0da03..bb4f040 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -54,7 +54,7 @@
*/
public class ArrowTipView extends AbstractFloatingView {
- private static final String TAG = ArrowTipView.class.getSimpleName();
+ private static final String TAG = "ArrowTipView";
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 0d07f63..1d5a9dc 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -68,7 +68,7 @@
public class FloatingIconView extends FrameLayout implements
Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView {
- private static final String TAG = FloatingIconView.class.getSimpleName();
+ private static final String TAG = "FloatingIconView";
// Manages loading the icon on a worker thread
private static @Nullable IconLoadResult sIconLoadResult;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 8892a18..1368084 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -17,6 +17,8 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableWidgetTapToAdd;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP;
import android.content.Context;
@@ -42,6 +44,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -74,6 +77,7 @@
private boolean mDisableNavBarScrim = false;
@Nullable private WidgetCell mWidgetCellWithAddButton = null;
+ @Nullable private WidgetItem mLastSelectedWidgetItem = null;
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -161,13 +165,26 @@
}
mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null;
+ if (mWidgetCellWithAddButton != null) {
+ mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem();
+ } else {
+ mLastSelectedWidgetItem = null;
+ }
} else {
mActivityContext.getItemOnClickListener().onClick(wc);
}
}
+ @Override
+ protected float getShiftRange() {
+ // We add the extra height added during predictive back / swipe up to the shift range, so
+ // that the idle interpolator knows to animate the view off fully.
+ return mContent.getHeight() + getBottomOffsetPx();
+ }
+
/**
- * Click handler for tap to add button.
+ * Click handler for tap to add button. This handler assumes we are in the Launcher activity and
+ * should not be used when the widget sheet is displayed elsewhere.
*/
private void addWidget(@NonNull PendingAddItemInfo info) {
// Using a boolean flag here to make sure the callback is only run once. This should never
@@ -175,19 +192,23 @@
// needed.
final AtomicBoolean hasRun = new AtomicBoolean(false);
addOnCloseListener(() -> {
- if (!hasRun.get()) {
- Launcher.getLauncher(mActivityContext).getAccessibilityDelegate().addToWorkspace(
- info, /*accessibility=*/ false,
+ if (hasRun.get()) return;
+ hasRun.set(true);
+
+ // Going to NORMAL state will also dismiss the All Apps view if it is showing.
+ Launcher launcher = Launcher.getLauncher(mActivityContext);
+ launcher.getStateManager().goToState(NORMAL, forSuccessCallback(() -> {
+ launcher.getAccessibilityDelegate().addToWorkspace(info,
+ /*accessibility=*/ false,
/*finishCallback=*/ (success) -> {
mActivityContext.getStatsLogManager()
.logger()
.withItemInfo(info)
.log(LAUNCHER_WIDGET_ADD_BUTTON_TAP);
});
- hasRun.set(true);
- }
+ }));
});
- handleClose(true);
+ close(/* animate= */ true);
}
/**
@@ -236,6 +257,14 @@
return 0;
}
+ /**
+ * Returns the component of the widget that is currently showing an add button, if any.
+ */
+ @Nullable
+ protected WidgetItem getLastSelectedWidgetItem() {
+ return mLastSelectedWidgetItem;
+ }
+
@Override
public boolean onLongClick(View v) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 5dacfb0..eac2ce7 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -503,6 +503,15 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ if (changed && isShowingAddButton()) {
+ post(this::setupIconOrTextButton);
+ }
+ }
+
/**
* Loads a high resolution package icon to show next to the widget title.
*/
@@ -627,4 +636,19 @@
set.playSequentially(hideAnim, showAnim);
set.start();
}
+
+ /**
+ * Returns true if this WidgetCell is displaying the same item as info.
+ */
+ public boolean matchesItem(WidgetItem info) {
+ if (info == null || mItem == null) return false;
+ if (info.widgetInfo != null && mItem.widgetInfo != null) {
+ return info.widgetInfo.getUser().equals(mItem.widgetInfo.getUser())
+ && info.widgetInfo.getComponent().equals(mItem.widgetInfo.getComponent());
+ } else if (info.activityInfo != null && mItem.activityInfo != null) {
+ return info.activityInfo.getUser().equals(mItem.activityInfo.getUser())
+ && info.activityInfo.getComponent().equals(mItem.activityInfo.getComponent());
+ }
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index b4c4623..4ea2426 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -142,6 +142,9 @@
row.forEach(widgetItem -> {
WidgetCell widget = addItemCell(tableRow);
widget.applyFromCellItem(widgetItem);
+ if (widget.matchesItem(getLastSelectedWidgetItem())) {
+ widget.callOnClick();
+ }
});
widgetsTable.addView(tableRow);
});
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 79ddadc..9c4db60 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -29,6 +29,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
@@ -40,6 +41,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.recyclerview.ViewHolderBinder;
import com.android.launcher3.util.PackageUserKey;
@@ -64,7 +66,7 @@
// This ratio defines the max percentage of content area that the recommendations can display
// with respect to the bottom sheet's height.
- private static final float RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE = 0.75f;
+ private static final float RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE = 0.60f;
private FrameLayout mSuggestedWidgetsContainer;
private WidgetsListHeader mSuggestedWidgetsHeader;
private PackageUserKey mSuggestedWidgetsPackageUserKey;
@@ -194,15 +196,21 @@
layoutParams.width = 0;
}
layoutParams.weight = layoutParams.width == 0 ? 0.33F : 0;
- leftPane.setLayoutParams(layoutParams);
- requestApplyInsets();
- if (mSelectedHeader != null) {
- if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
- mSuggestedWidgetsHeader.callOnClick();
- } else {
- getHeaderChangeListener().onHeaderChanged(mSelectedHeader);
+
+ post(() -> {
+ // The following calls all trigger requestLayout, so we post them to avoid
+ // calling requestLayout during a layout pass. This also fixes the related warnings
+ // in logcat.
+ leftPane.setLayoutParams(layoutParams);
+ requestApplyInsets();
+ if (mSelectedHeader != null) {
+ if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
+ mSuggestedWidgetsHeader.callOnClick();
+ } else {
+ getHeaderChangeListener().onHeaderChanged(mSelectedHeader);
+ }
}
- }
+ });
}
}
@@ -222,6 +230,9 @@
if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) {
setupSuggestedWidgets(LayoutInflater.from(getContext()));
mSuggestedWidgetsHeader.callOnClick();
+ } else if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
+ // Reselect widget if we are reloading recommendations while it is currently showing.
+ selectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem());
}
}
@@ -269,6 +280,16 @@
mRightPaneScrollView.setScrollY(0);
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
+ final boolean isChangingHeaders =
+ !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
+ if (isChangingHeaders) {
+ // If switching from another header, unselect any WidgetCells. This is necessary
+ // because we do not clear/recycle the WidgetCells in the recommendations container
+ // when the header is clicked, only when onRecommendationsBound is called. That
+ // means a WidgetCell in the recommendations container may still be selected from
+ // the last time the recommendations were shown.
+ unselectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem());
+ }
mSelectedHeader = mSuggestedWidgetsPackageUserKey;
});
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
@@ -357,6 +378,8 @@
return new HeaderChangeListener() {
@Override
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
+ final boolean isSameHeader = mSelectedHeader != null
+ && mSelectedHeader.equals(selectedHeader);
mSelectedHeader = selectedHeader;
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
.getSelectedAppWidgets(selectedHeader);
@@ -384,11 +407,20 @@
contentEntryToBind,
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
Collections.EMPTY_LIST);
+ if (isSameHeader) {
+ // Reselect the last selected widget if we are reloading the same header.
+ selectWidgetCell(widgetsRowViewHolder.tableContainer,
+ getLastSelectedWidgetItem());
+ }
widgetsRowViewHolder.mDataCallback = data -> {
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
contentEntryToBind,
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
Collections.singletonList(data));
+ if (isSameHeader) {
+ selectWidgetCell(widgetsRowViewHolder.tableContainer,
+ getLastSelectedWidgetItem());
+ }
};
mRightPane.removeAllViews();
mRightPane.addView(widgetsRowViewHolder.itemView);
@@ -401,6 +433,24 @@
};
}
+ private static void selectWidgetCell(ViewGroup parent, WidgetItem item) {
+ if (parent == null || item == null) return;
+ WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
+ && wc.matchesItem(item));
+ if (cell != null && !cell.isShowingAddButton()) {
+ cell.callOnClick();
+ }
+ }
+
+ private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) {
+ if (parent == null || item == null) return;
+ WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
+ && wc.matchesItem(item));
+ if (cell != null && cell.isShowingAddButton()) {
+ cell.hideAddButton(/* animate= */ false);
+ }
+ }
+
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
diff --git a/tests/Android.bp b/tests/Android.bp
index 5ec2263..11177de 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -105,6 +105,7 @@
android_library {
name: "Launcher3TestResources",
resource_dirs: ["res"],
+ asset_dirs: ["assets"],
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index a9b75ea..8c5195e 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -412,5 +412,9 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
+
+ <property
+ android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
+ android:value="true"/>
</application>
</manifest>
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 8c47332..4a04953 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -175,10 +175,11 @@
public static final String OVERVIEW_OVER_HOME = "b/279059025";
public static final String UIOBJECT_STALE_ELEMENT = "b/319501259";
public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466";
- public static final String TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE = "b/326073471";
public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
public static final String ACTIVITY_NOT_RESUMED_AFTER_BACK = "b/322823209";
public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341";
+ public static final String CLOCK_ICON_DRAWABLE_LEAKING = "b/319168409";
+
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
diff --git a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
similarity index 97%
rename from tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
index 13dfd5e..c32461e 100644
--- a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
@@ -22,6 +22,8 @@
import android.view.View
import androidx.core.view.get
import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
import com.android.launcher3.CellLayout
import com.android.launcher3.celllayout.board.CellLayoutBoard
import com.android.launcher3.celllayout.board.IconPoint
@@ -34,6 +36,7 @@
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
+import org.junit.runner.RunWith
private class HotseatReorderTestCase(
val startBoard: CellLayoutBoard,
@@ -44,6 +47,8 @@
}
}
+@SmallTest
+@RunWith(AndroidJUnit4::class)
class HotseatReorderUnitTest {
private val applicationContext: Context =
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTestCase.java
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
index 0bec1b2..a9355ec 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderPreviewAnimationTest.kt
@@ -141,11 +141,14 @@
ReorderPreviewAnimation.MODE_PREVIEW,
AnimationValues(dx = 0, dy = 0, scale = 100)
)
- testAnimationAtGivenProgress(
- PREVIEW_DURATION * 99,
- ReorderPreviewAnimation.MODE_PREVIEW,
- AnimationValues(dx = 5, dy = -10, scale = 96)
- )
+ // (b/339313407) Temporarily disable this test as the behavior is
+ // inconsistent between Soong & Gradle builds.
+ //
+ // testAnimationAtGivenProgress(
+ // PREVIEW_DURATION * 99,
+ // ReorderPreviewAnimation.MODE_PREVIEW,
+ // AnimationValues(dx = 5, dy = -10, scale = 96)
+ // )
testAnimationAtGivenProgress(
PREVIEW_DURATION * 98,
ReorderPreviewAnimation.MODE_PREVIEW,
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderTestCase.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderTestCase.java
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/ReorderTestCase.java
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderTestCase.java
diff --git a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/DeterministicRandomGenerator.kt
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
similarity index 100%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomMultiBoardGenerator.kt
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 56ac960..b4945d7 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -79,6 +79,7 @@
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
+ private PackageManagerHelper mPmHelper;
private MatrixCursor mCursor;
private InvariantDeviceProfile mIDP;
@@ -92,6 +93,7 @@
mContext = mModelHelper.sandboxContext;
mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
mApp = LauncherAppState.getInstance(mContext);
+ mPmHelper = PackageManagerHelper.INSTANCE.get(mContext);
mCursor = new MatrixCursor(new String[] {
ICON, TITLE, _ID, CONTAINER, ITEM_TYPE,
@@ -101,7 +103,7 @@
});
UserManagerState ums = new UserManagerState();
- mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, null);
+ mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, mPmHelper, null);
ums.allUsers.put(0, Process.myUserHandle());
}
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index e459956..89a4544 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -244,7 +244,6 @@
SystemShortcut systemShortcut = SystemShortcut.PRIVATE_PROFILE_INSTALL
.getShortcut(mTestContext, mAppInfo, mView);
- verify(mPrivateProfileManager, times(2)).getProfileUser();
assertNull(systemShortcut);
}
@@ -266,7 +265,6 @@
SystemShortcut systemShortcut = SystemShortcut.PRIVATE_PROFILE_INSTALL
.getShortcut(mTestContext, mAppInfo, mView);
- verify(mPrivateProfileManager, times(3)).getProfileUser();
verify(mPrivateProfileManager).isEnabled();
assertNotNull(systemShortcut);
}
diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
index d1da5f4..b5e797e 100644
--- a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
+++ b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
@@ -34,6 +34,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
@@ -63,6 +64,8 @@
mContext = mock(Context.class);
mLauncherApps = mock(LauncherApps.class);
when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
mPackageManagerHelper = new PackageManagerHelper(mContext);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index f3ec852..71cd19d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -58,6 +58,7 @@
private static final String FAST_SCROLLER_RES_ID = "fast_scroller";
private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final String UNLOCK_BUTTON_VIEW_RES_ID = "ps_lock_unlock_button";
private final int mHeight;
private final int mIconHeight;
@@ -433,7 +434,28 @@
public PrivateSpaceContainer getPrivateSpaceUnlockedView() {
final UiObject2 allAppsContainer = verifyActiveContainer();
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
- return new PrivateSpaceContainer(mLauncher, appListRecycler, this);
+ return new PrivateSpaceContainer(mLauncher, appListRecycler, this, true);
+ }
+
+ /** Returns PrivateSpaceContainer in locked state, if present in view. */
+ @NonNull
+ public PrivateSpaceContainer getPrivateSpaceLockedView() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+ return new PrivateSpaceContainer(mLauncher, appListRecycler, this, false);
+ }
+
+ /**
+ * Toggles Lock/Unlock of Private Space, changing the All Apps Ui.
+ */
+ public void togglePrivateSpace() {
+ final UiObject2 allAppsContainer = verifyActiveContainer();
+ final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+ UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(appListRecycler,
+ UNLOCK_BUTTON_VIEW_RES_ID);
+ mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+ mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+ unLockButtonView.click();
}
protected abstract void verifyVisibleContainerOnDismiss();
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 68829e0..2e3944d 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -359,6 +359,21 @@
}
/**
+ * Gets Overview Actions specific to grouped tasks.
+ *
+ * @return The Overview group actions bar
+ */
+ @NonNull
+ public OverviewActions getOverviewGroupActions() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to get overview group actions")) {
+ verifyActiveContainer();
+ UiObject2 groupActions = mLauncher.waitForOverviewObject("group_action_buttons");
+ return new OverviewActions(groupActions, mLauncher);
+ }
+ }
+
+ /**
* Returns if clear all button is visible.
*/
public boolean isClearAllVisible() {
@@ -449,10 +464,18 @@
private void verifyActionsViewVisibility() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
+ boolean isTablet = mLauncher.isTablet();
+ OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
+
if (isActionsViewVisible()) {
- mLauncher.waitForOverviewObject("action_buttons");
+ if (task.isTaskSplit()) {
+ mLauncher.waitForOverviewObject("group_action_buttons");
+ } else {
+ mLauncher.waitForOverviewObject("action_buttons");
+ }
} else {
mLauncher.waitUntilOverviewObjectGone("action_buttons");
+ mLauncher.waitUntilOverviewObjectGone("group_action_buttons");
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index aa8d339..68b0a36 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -108,7 +108,7 @@
public final class LauncherInstrumentation {
private static final String TAG = "Tapl";
- private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
+ private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 5;
private static final int GESTURE_STEP_MS = 16;
static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
index 0e65ffb..a2814f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
@@ -25,22 +25,30 @@
*/
public class PrivateSpaceContainer {
private static final String PS_HEADER_RES_ID = "ps_header_layout";
- private static final String INSTALL_APP_TITLE = "Install apps";
+ private static final String INSTALL_APP_TITLE = "Install";
private static final String DIVIDER_RES_ID = "private_space_divider";
private final LauncherInstrumentation mLauncher;
private final UiObject2 mAppListRecycler;
private final AllApps mAppList;
+ private final boolean mPrivateSpaceEnabled;
PrivateSpaceContainer(LauncherInstrumentation launcherInstrumentation,
- UiObject2 appListRecycler, AllApps appList) {
+ UiObject2 appListRecycler, AllApps appList, boolean privateSpaceEnabled) {
mLauncher = launcherInstrumentation;
mAppListRecycler = appListRecycler;
mAppList = appList;
+ mPrivateSpaceEnabled = privateSpaceEnabled;
- verifyHeaderIsPresent();
- verifyInstallAppButtonIsPresent();
- verifyDividerIsPresent();
+ if (mPrivateSpaceEnabled) {
+ verifyHeaderIsPresent();
+ verifyInstallAppButtonIsPresent();
+ verifyDividerIsPresent();
+ } else {
+ verifyHeaderIsPresent();
+ verifyInstallAppButtonIsNotPresent();
+ verifyDividerIsNotPresent();
+ }
}
// Assert PS Header is in view.
@@ -48,7 +56,7 @@
private void verifyHeaderIsPresent() {
final UiObject2 psHeader = mLauncher.waitForObjectInContainer(mAppListRecycler,
PS_HEADER_RES_ID);
- new PrivateSpaceHeader(mLauncher, psHeader, true);
+ new PrivateSpaceHeader(mLauncher, psHeader, mPrivateSpaceEnabled);
}
@@ -57,11 +65,21 @@
mAppList.getAppIcon(INSTALL_APP_TITLE);
}
+ // Assert Install App Item is not present in view.
+ private void verifyInstallAppButtonIsNotPresent() {
+ mLauncher.waitUntilLauncherObjectGone(DIVIDER_RES_ID);
+ }
+
// Assert Sys App Divider is present in view.
private void verifyDividerIsPresent() {
mLauncher.waitForObjectInContainer(mAppListRecycler, DIVIDER_RES_ID);
}
+ // Assert Sys App Divider is not present in view.
+ private void verifyDividerIsNotPresent() {
+ mLauncher.waitUntilLauncherObjectGone(DIVIDER_RES_ID);
+ }
+
/**
* Verifies that a user installed app is present above the divider.
*/
@@ -73,4 +91,4 @@
mLauncher.assertTrue("Installed App: " + appName + " is not above the divider",
iconCenter.y < dividerCenter.y);
}
-}
+}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
index 3a7f038..cc64aae 100644
--- a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
@@ -45,7 +45,7 @@
if (mPrivateSpaceEnabled) {
verifyUnlockedState();
} else {
- mLauncher.fail("Private Space found in non enabled state");
+ verifyLockedState();
}
}
@@ -74,4 +74,23 @@
mLauncher.assertEquals("PS lock text is incorrect", "Lock", lockText.getText());
}
+
+ /** Verify Locked State elements in Private Space Header */
+ private void verifyLockedState() {
+ UiObject2 headerText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ PS_HEADER_TEXT_RES_ID);
+ mLauncher.assertEquals("PS Header Text is incorrect ",
+ "Private", headerText.getText());
+
+ UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ UNLOCK_BUTTON_VIEW_RES_ID);
+ mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+ mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+
+ mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+ LOCK_ICON_RES_ID);
+
+ mLauncher.waitUntilLauncherObjectGone(SETTINGS_BUTTON_RES_ID);
+ mLauncher.waitUntilLauncherObjectGone(LOCK_TEXT_RES_ID);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 8d3a631..4be46ab 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import android.text.TextUtils;
import android.widget.TextView;
import androidx.test.uiautomator.By;
@@ -117,4 +118,28 @@
public SearchResultFromQsb getSearchResultForInput() {
return this;
}
+
+ /** Verify a tile is present by checking its title and subtitle. */
+ public void verifyTileIsPresent(String title, String subtitle) {
+ ArrayList<UiObject2> searchResults =
+ new ArrayList<>(mLauncher.waitForObjectsInContainer(
+ mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
+ By.clazz(TextView.class)));
+ boolean foundTitle = false;
+ boolean foundSubtitle = false;
+ for (UiObject2 uiObject: searchResults) {
+ String currentString = uiObject.getText();
+ if (TextUtils.equals(currentString, title)) {
+ foundTitle = true;
+ } else if (TextUtils.equals(currentString, subtitle)) {
+ foundSubtitle = true;
+ }
+ }
+ if (!foundTitle) {
+ mLauncher.fail("Tile not found for title: " + title);
+ }
+ if (!foundSubtitle) {
+ mLauncher.fail("Tile not found for subtitle: " + subtitle);
+ }
+ }
}