Merge "Delay TransitionAnimator.onAnimationEnd by one frame (2/4)" 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/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 1c5a75d..866dbe5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -521,7 +521,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/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index b24be54..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;
}
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 4da7762..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;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 32e4097..6abd5a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -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()));
}
/**
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/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 8c83508..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(
@@ -419,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 4334807..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.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index ac02f1f..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;
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/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 d0462aa..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.
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/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index bf985af..28ae3d2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -148,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;
@@ -1259,13 +1260,16 @@
? mRecentsView.getNextPageTaskView() : null;
TaskView currentPageTaskView = mRecentsView != null
? mRecentsView.getCurrentPageTaskView() : null;
- if ((nextPageTaskView instanceof DesktopTaskView
- || currentPageTaskView instanceof DesktopTaskView)
- && 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;
}
@@ -1422,14 +1426,27 @@
mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
setClampScrollOffset(false);
};
- if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
- && !(mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView))) {
- 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);
@@ -2251,11 +2268,14 @@
mRecentsAnimationController, mRecentsAnimationTargets);
});
- if (mRecentsView.getNextPageTaskView() instanceof DesktopTaskView
- || mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView) {
- // 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.
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/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/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index cd9df26..a53d91f 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -65,7 +65,6 @@
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;
@@ -336,24 +335,15 @@
recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
boolean shouldShowActionsButtonInstead =
isLargeTileFocusedTask && isInExpectedScrollPosition;
- boolean hasUnpinnableApp = Arrays.stream(taskView.getTaskContainers())
- .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;
}
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/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 5c5ee7e..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.TaskContainer[] containers = gtv.getTaskContainers();
- WorkspaceItemInfo recentsInfo1 = containers[0].getItemInfo();
- WorkspaceItemInfo recentsInfo2 = containers[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/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/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 5968901..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;
@@ -42,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;
@@ -65,12 +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;
- private final ArrayList<TaskThumbnailViewDeprecated> mSnapshotViews = new ArrayList<>();
-
private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
private final TaskView.FullscreenDrawParams mSnapshotDrawParams;
@@ -81,6 +80,8 @@
private final PointF mTempPointF = new PointF();
+ private final ViewPool<TaskThumbnailViewDeprecated> mTaskthumbnailViewPool;
+
public DesktopTaskView(Context context) {
this(context, null);
}
@@ -103,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
@@ -163,45 +168,36 @@
}
cancelPendingLoadTasks();
- mTaskContainers = new TaskContainer[tasks.size()];
-
- // 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() > tasks.size()) {
- int diff = mSnapshotViews.size() - tasks.size();
- for (int i = 0; i < diff; i++) {
- TaskThumbnailViewDeprecated snapshotView = mSnapshotViews.remove(0);
- removeView(snapshotView);
- }
- } else if (mSnapshotViews.size() < tasks.size()) {
- int diff = tasks.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,
- new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- }
- }
-
+ ((ArrayList<TaskContainer>) mTaskContainers).ensureCapacity(tasks.size());
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
- TaskThumbnailViewDeprecated thumbnailView = mSnapshotViews.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);
- mTaskContainers[i] = createAttributeContainer(task, thumbnailView);
+ TaskContainer taskContainer = new TaskContainer(task, thumbnailView, mIconView,
+ STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null);
+ if (i >= mTaskContainers.size()) {
+ mTaskContainers.add(taskContainer);
+ } else {
+ mTaskContainers.set(i, taskContainer);
+ }
+ }
+ while (mTaskContainers.size() > tasks.size()) {
+ TaskContainer taskContainer = mTaskContainers.remove(mTaskContainers.size() - 1);
+ removeView(taskContainer.getThumbnailView());
+ mTaskthumbnailViewPool.recycle(taskContainer.getThumbnailView());
}
setOrientationState(orientedState);
}
- private TaskContainer createAttributeContainer(Task task,
- TaskThumbnailViewDeprecated thumbnailView) {
- return new TaskContainer(task, thumbnailView, mIconView,
- STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null);
- }
-
@Override
public void onTaskListVisibilityChanged(boolean visible, int changes) {
cancelPendingLoadTasks();
@@ -314,7 +310,7 @@
int thumbnailTopMarginPx = mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
containerHeight -= thumbnailTopMarginPx;
- if (mTaskContainers.length == 0) {
+ if (mTaskContainers.isEmpty()) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index f92b9dd..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;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index c26fc0c3..1ccb764 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -63,7 +63,7 @@
*/
public class GroupedTaskView extends TaskView {
- private static final String TAG = GroupedTaskView.class.getSimpleName();
+ private static final String TAG = "GroupedTaskView";
// TODO(b/336612373): Support new TTV for GroupedTaskView
private TaskThumbnailViewDeprecated mSnapshotView2;
private TaskViewIcon mIconView2;
@@ -98,7 +98,7 @@
@Deprecated
@Nullable
private Task getSecondTask() {
- return mTaskContainers.length > 1 ? mTaskContainers[1].getTask() : null;
+ return mTaskContainers.size() > 1 ? mTaskContainers.get(1).getTask() : null;
}
@Override
@@ -150,11 +150,11 @@
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
@Nullable SplitBounds splitBoundsConfig) {
super.bind(primary, orientedState);
- mTaskContainers = new TaskContainer[]{
- mTaskContainers[0],
+ mTaskContainers = Arrays.asList(
+ mTaskContainers.get(0),
new TaskContainer(secondary, findViewById(R.id.bottomright_snapshot),
- mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2)};
- mTaskContainers[0].setStagePosition(
+ mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT, mDigitalWellBeingToast2));
+ mTaskContainers.get(0).setStagePosition(
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
mSnapshotView2.bind(secondary);
mSplitBoundsConfig = splitBoundsConfig;
@@ -176,12 +176,12 @@
public void setUpShowAllInstancesListener() {
// sets up the listener for the left/top task
super.setUpShowAllInstancesListener();
- if (mTaskContainers.length < 2) {
+ if (mTaskContainers.size() < 2) {
return;
}
// right/bottom task's base package name
- String taskPackageName = mTaskContainers[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);
@@ -279,7 +279,7 @@
@Nullable
@Override
public RunnableList launchTaskAnimated() {
- if (mTaskContainers.length == 0) {
+ if (mTaskContainers.isEmpty()) {
return null;
}
@@ -407,9 +407,8 @@
} else {
// Currently being split with this taskView, let the non-split selected thumbnail
// take up full thumbnail area
- Optional<TaskContainer> nonSplitContainer = Arrays.stream(
- mTaskContainers).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(
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 075f159..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;
@@ -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
@@ -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);
- TaskContainer[] containers = taskView.getTaskContainers();
- if (containers.length == 0) {
+ List<TaskContainer> containers = taskView.getTaskContainers();
+ if (containers.isEmpty()) {
continue;
}
int index = indexOfChild(taskView);
@@ -2367,7 +2377,7 @@
}
if (visible) {
// Default update all non-null tasks, then remove running ones
- List<Task> tasksToUpdate = Arrays.stream(containers).filter(Objects::nonNull)
+ List<Task> tasksToUpdate = containers.stream()
.map(TaskContainer::getTask)
.collect(Collectors.toCollection(ArrayList::new));
if (mTmpRunningTasks != null) {
@@ -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);
@@ -4801,7 +4815,7 @@
boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
== mSplitSelectStateController.getInitialTaskId();
TaskContainer taskContainer = mSplitHiddenTaskView
- .getTaskContainers()[primaryTaskSelected ? 1 : 0];
+ .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailView();
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskContainer, builder,
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index d0bf2c2..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;
@@ -307,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();
@@ -364,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,
@@ -380,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();
@@ -389,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();
}
@@ -409,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/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 4046a89..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,6 +327,7 @@
}
};
+ public TaskViewData mTaskViewData = new TaskViewData();
protected TaskThumbnailViewDeprecated mTaskThumbnailViewDeprecated;
protected TaskThumbnailView mTaskThumbnailView;
protected TaskViewIcon mIconView;
@@ -368,7 +370,7 @@
private float mStableAlpha = 1;
private int mTaskViewId = -1;
- protected TaskContainer[] mTaskContainers = new TaskContainer[0];
+ protected List<TaskContainer> mTaskContainers = Collections.emptyList();
private boolean mShowScreenshot;
private boolean mBorderEnabled;
@@ -496,7 +498,7 @@
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 (mTaskContainers.length > 0) {
+ if (!mTaskContainers.isEmpty()) {
bindTaskThumbnailView();
}
}
@@ -685,9 +687,9 @@
*/
public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
- mTaskContainers = new TaskContainer[]{
+ mTaskContainers = Collections.singletonList(
new TaskContainer(task, mTaskThumbnailViewDeprecated, mIconView,
- STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast)};
+ STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast));
if (enableRefactorTaskThumbnail()) {
bindTaskThumbnailView();
} else {
@@ -706,10 +708,10 @@
* Sets up an on-click listener and the visibility for show_windows icon on top of the task.
*/
public void setUpShowAllInstancesListener() {
- if (mTaskContainers.length == 0) {
+ if (mTaskContainers.isEmpty()) {
return;
}
- String taskPackageName = mTaskContainers[0].getTask().key.getPackageName();
+ String taskPackageName = mTaskContainers.get(0).getTask().key.getPackageName();
// icon of the top/left task
View showWindowsView = findViewById(R.id.show_windows);
@@ -752,9 +754,9 @@
}
/**
- * Returns an array of all TaskContainers in the TaskView.
+ * Returns a list of all TaskContainers in the TaskView.
*/
- public TaskContainer[] getTaskContainers() {
+ public List<TaskContainer> getTaskContainers() {
return mTaskContainers;
}
@@ -766,7 +768,7 @@
@Deprecated
@Nullable
public Task getFirstTask() {
- return mTaskContainers.length > 0 ? mTaskContainers[0].getTask() : null;
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getTask() : null;
}
/**
@@ -780,12 +782,12 @@
* Returns a copy of integer array containing taskIds of all tasks in the TaskView.
*/
public int[] getTaskIds() {
- return Arrays.stream(mTaskContainers).mapToInt(
+ return mTaskContainers.stream().mapToInt(
container -> container.getTask().key.id).toArray();
}
public boolean containsMultipleTasks() {
- return mTaskContainers.length > 1;
+ return mTaskContainers.size() > 1;
}
/**
@@ -794,7 +796,7 @@
*/
@Nullable
public TaskContainer getTaskContainerById(int taskId) {
- return Arrays.stream(mTaskContainers).filter(
+ return mTaskContainers.stream().filter(
container -> container.getTask().key.id == taskId).findFirst().orElse(null);
}
@@ -805,7 +807,7 @@
*/
@Deprecated
public TaskThumbnailViewDeprecated getFirstThumbnailView() {
- return mTaskContainers.length > 0 ? mTaskContainers[0].getThumbnailView()
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getThumbnailView()
: mTaskThumbnailViewDeprecated;
}
@@ -826,7 +828,7 @@
}
public TaskThumbnailViewDeprecated[] getThumbnailViews() {
- return Arrays.stream(mTaskContainers).map(
+ return mTaskContainers.stream().map(
TaskContainer::getThumbnailView).toArray(
TaskThumbnailViewDeprecated[]::new);
}
@@ -839,7 +841,7 @@
@Deprecated
@Nullable
public TaskViewIcon getFirstIconView() {
- return mTaskContainers.length > 0 ? mTaskContainers[0].getIconView() : null;
+ return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getIconView() : null;
}
@Override
@@ -892,10 +894,10 @@
*/
protected boolean confirmSecondSplitSelectApp() {
int index = getLastSelectedChildTaskIndex();
- if (index >= mTaskContainers.length) {
+ if (index >= mTaskContainers.size()) {
return false;
}
- TaskContainer container = mTaskContainers[index];
+ TaskContainer container = mTaskContainers.get(index);
if (container != null) {
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView().getDrawable(), container.getThumbnailView(),
@@ -1217,9 +1219,8 @@
}
protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- Optional<TaskContainer> menuContainer = Arrays.stream(
- mTaskContainers).filter(
- container -> container.getIconView() == iconView).findAny();
+ Optional<TaskContainer> menuContainer = mTaskContainers.stream().filter(
+ container -> container.getIconView() == iconView).findAny();
if (menuContainer.isEmpty()) {
return false;
}
@@ -1462,6 +1463,9 @@
scale *= mDismissScale;
setScaleX(scale);
setScaleY(scale);
+ if (enableRefactorTaskThumbnail()) {
+ mTaskViewData.getScale().setValue(scale);
+ }
updateSnapshotRadius();
}
@@ -1768,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
@@ -1785,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() {
@@ -1799,8 +1797,8 @@
if (getRecentsView() == null) {
return;
}
- fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
- getScaleX());
+ fullscreenParams.setProgress(
+ mFullscreenProgress, getRecentsView().getScaleX(), getScaleX());
}
/**
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/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index c64ac23..50f74c2 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -35,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;
@@ -167,6 +168,7 @@
@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());
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 eb20de2..f29df61 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -91,7 +91,7 @@
whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
- whenever(mockTaskView.taskContainers).thenReturn(Array(1) { mockTaskContainer })
+ whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
@@ -184,7 +184,7 @@
whenever(mockTask.getKey()).thenReturn(mockTaskKey)
whenever(mockTaskKey.getId()).thenReturn(taskId)
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
- whenever(mockGroupedTaskView.taskContainers).thenReturn(Array(1) { mockTaskContainer })
+ whenever(mockGroupedTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
splitAnimationController.getFirstAnimInitViews(
{ mockGroupedTaskView },
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/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/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 869b995..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);
}
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/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 eabacbf..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,6 +165,11 @@
}
mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null;
+ if (mWidgetCellWithAddButton != null) {
+ mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem();
+ } else {
+ mLastSelectedWidgetItem = null;
+ }
} else {
mActivityContext.getItemOnClickListener().onClick(wc);
}
@@ -174,7 +183,8 @@
}
/**
- * 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
@@ -182,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);
}
/**
@@ -243,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 5292ee2..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;
@@ -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 74b3ccc..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,7 +175,6 @@
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";
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/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/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/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);
+ }
+ }
}