Merge "Revert "Set mState = mCurrentStableState in onAnimationCancel()"" into tm-dev
diff --git a/Android.bp b/Android.bp
index b3027bc..0a55675 100644
--- a/Android.bp
+++ b/Android.bp
@@ -228,7 +228,7 @@
],
}
-// Common source files used to build go launcher
+// Common source files used to build go launcher except go/src files
filegroup {
name: "launcher-go-src-no-build-config",
srcs: [
@@ -236,8 +236,6 @@
"src/**/*.kt",
"quickstep/src/**/*.java",
"quickstep/src/**/*.kt",
- "go/src/**/*.java",
- "go/src/**/*.kt",
"go/quickstep/src/**/*.java",
"go/quickstep/src/**/*.kt",
],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b459b2d..4f580e0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3">
- <uses-sdk android:targetSdkVersion="30" android:minSdkVersion="26"/>
+ <uses-sdk android:targetSdkVersion="33" android:minSdkVersion="26"/>
<!--
Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
Refer comments around specific entries on how to extend individual components.
diff --git a/go/quickstep/res/values-pt-rPT/strings.xml b/go/quickstep/res/values-pt-rPT/strings.xml
index 7041f41..e64f520 100644
--- a/go/quickstep/res/values-pt-rPT/strings.xml
+++ b/go/quickstep/res/values-pt-rPT/strings.xml
@@ -10,9 +10,9 @@
<string name="dialog_settings" msgid="6564397136021186148">"DEFINIÇÕES"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduza ou ouça o texto no ecrã"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informações como o texto no ecrã, endereços Web e capturas de ecrã podem ser partilhadas com a Google.\n\nPara alterar as informações que partilha, aceda a "<b>"Definições > Apps > App predefinidas > App de assistente digital"</b>"."</string>
- <string name="assistant_not_selected_title" msgid="5017072974603345228">"Escolha um assistente para utilizar esta funcionalidade"</string>
+ <string name="assistant_not_selected_title" msgid="5017072974603345228">"Escolha um assistente para usar esta funcionalidade"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"Para ouvir ou traduzir o texto no ecrã, escolha uma app de assistente digital nas Definições"</string>
- <string name="assistant_not_supported_title" msgid="1675788067597484142">"Mude de assistente para utilizar esta funcionalidade"</string>
+ <string name="assistant_not_supported_title" msgid="1675788067597484142">"Mude de assistente para usar esta funcionalidade"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"Para ouvir ou traduzir o texto no ecrã, mude de app de assistente digital nas Definições"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"Toque aqui para ouvir o texto neste ecrã"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"Toque aqui para traduzir o texto neste ecrã"</string>
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index c252fba..e717937 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -39,6 +39,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.views.ActivityContext;
import java.io.File;
@@ -77,11 +78,12 @@
return FileProvider.getUriForFile(context, authority, pathFile, displayName);
}
- private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info) {
+ private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info,
+ View originalView) {
if (TextUtils.isEmpty(mSharingComponent)) {
return null;
}
- return new Share(launcher, info);
+ return new Share(launcher, info, originalView);
}
/**
@@ -104,8 +106,9 @@
private final PopupDataProvider mPopupDataProvider;
private final boolean mSharingEnabledForUser;
- public Share(Launcher target, ItemInfo itemInfo) {
- super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo);
+ public Share(Launcher target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
+ originalView);
mPopupDataProvider = target.getPopupDataProvider();
mSharingEnabledForUser = bluetoothSharingEnabled(target);
@@ -189,9 +192,14 @@
}
private void showCannotShareToast(Context context) {
+ ActivityContext activityContext = ActivityContext.lookupContext(context);
+ String blockedByMessage = activityContext.getStringCache() != null
+ ? activityContext.getStringCache().disabledByAdminMessage
+ : context.getString(R.string.blocked_by_policy);
+
CharSequence text = (mSharingEnabledForUser)
? context.getText(R.string.toast_p2p_app_not_shareable)
- : context.getText(R.string.blocked_by_policy);
+ : blockedByMessage;
int duration = Toast.LENGTH_SHORT;
Toast.makeText(context, text, duration).show();
}
@@ -200,6 +208,7 @@
/**
* Shortcut factory for generating the Share App button
*/
- public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY = (launcher, itemInfo) ->
- (new AppSharing(launcher)).getShortcut(launcher, itemInfo);
+ public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY =
+ (launcher, itemInfo, originalView) ->
+ (new AppSharing(launcher)).getShortcut(launcher, itemInfo, originalView);
}
diff --git a/go/src/com/android/launcher3/util/AbsGridOccupancy.java b/go/src/com/android/launcher3/util/AbsGridOccupancy.java
new file mode 100644
index 0000000..4a46bd1
--- /dev/null
+++ b/go/src/com/android/launcher3/util/AbsGridOccupancy.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Defines method to find the next vacant cell on a grid.
+ * This uses the default top-down, left-right approach and can be over-written through
+ * code swaps in different launchers.
+ */
+public abstract class AbsGridOccupancy {
+
+ /**
+ * Find the first vacant cell, if there is one.
+ *
+ * @param vacantOut Holds the x and y coordinate of the vacant cell
+ * @param spanX Horizontal cell span.
+ * @param spanY Vertical cell span.
+ *
+ * @return true if a vacant cell was found
+ */
+ protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
+ int spanX, int spanY) {
+ for (int y = 0; (y + spanY) <= countY; y++) {
+ for (int x = 0; (x + spanX) <= countX; x++) {
+ boolean available = !cells[x][y];
+ out:
+ for (int i = x; i < x + spanX; i++) {
+ for (int j = y; j < y + spanY; j++) {
+ available = available && !cells[i][j];
+ if (!available) break out;
+ }
+ }
+ if (available) {
+ vacantOut[0] = x;
+ vacantOut[1] = y;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cf854ed..10eedc8 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -76,6 +76,9 @@
// Represents the apps list sorted alphabetically inside the all-apps view.
message AllAppsContainer {
+ oneof ParentContainer {
+ TaskBarContainer taskbar_container = 1;
+ }
}
message WidgetsContainer {
@@ -83,6 +86,9 @@
// Represents the predicted apps row(top row) in the all-apps view.
message PredictionContainer {
+ oneof ParentContainer {
+ TaskBarContainer taskbar_container = 1;
+ }
}
// Represents the apps container within search results.
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 53910e3..7d7054f 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -20,7 +20,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3">
- <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+ <uses-sdk android:targetSdkVersion="33" android:minSdkVersion="26"/>
<!--
Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
Refer comments around specific entries on how to extend individual components.
@@ -57,6 +57,7 @@
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.SHOW_WORK_APPS" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index f4fb8fa..c58a42d 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -70,7 +70,7 @@
<string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Ferdig"</string>
<string name="gesture_tutorial_action_button_label" msgid="6249846312991332122">"Ferdig"</string>
<string name="gesture_tutorial_action_button_label_settings" msgid="2923621047916486604">"Innstillinger"</string>
- <string name="gesture_tutorial_try_again" msgid="65962545858556697">"Prøv igjen"</string>
+ <string name="gesture_tutorial_try_again" msgid="65962545858556697">"Prøv på nytt"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Bra!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Veiledning <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="allset_title" msgid="5021126669778966707">"Alt er klart!"</string>
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 4e3c02c..0cd9b2b 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,8 +15,6 @@
*/
-->
<resources>
- <dimen name="overview_actions_top_margin_gesture">19.1dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">10dp</dimen>
- <dimen name="overview_grid_side_margin">52dp</dimen>
- <dimen name="overview_page_spacing">38dp</dimen>
+ <!-- Overview actions -->
+ <dimen name="overview_actions_top_margin">12dp</dimen>
</resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 223a5e9..7494683 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -17,13 +17,13 @@
<resources>
<dimen name="navigation_key_padding">25dp</dimen>
+ <!-- Task View -->
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+ <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
<dimen name="overview_task_margin">12dp</dimen>
- <dimen name="overview_task_margin_grid">4dp</dimen>
- <dimen name="overview_actions_button_spacing">36dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">19.37dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">22dp</dimen>
- <dimen name="overview_grid_side_margin">60dp</dimen>
- <dimen name="overview_grid_row_spacing">36dp</dimen>
+ <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+ <dimen name="overview_task_margin_grid">12dp</dimen>
+ <dimen name="overview_grid_row_spacing">28dp</dimen>
<dimen name="overview_page_spacing">36dp</dimen>
- <dimen name="task_thumbnail_icon_drawable_size_grid">32dp</dimen>
+ <dimen name="overview_grid_side_margin">64dp</dimen>
</resources>
diff --git a/src/com/android/launcher3/allapps/DecorationInfo.java b/quickstep/res/values-sw720dp-land/dimens.xml
similarity index 64%
rename from src/com/android/launcher3/allapps/DecorationInfo.java
rename to quickstep/res/values-sw720dp-land/dimens.xml
index 50b250c..02d1189 100644
--- a/src/com/android/launcher3/allapps/DecorationInfo.java
+++ b/quickstep/res/values-sw720dp-land/dimens.xml
@@ -1,19 +1,20 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (c) 2022, 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-public class DecorationInfo {
-}
+*/
+-->
+<resources>
+ <!-- Overview actions -->
+ <dimen name="overview_actions_top_margin">20dp</dimen>
+</resources>
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
index e381cb0..ceaa8f8 100644
--- a/quickstep/res/values-sw720dp/dimens.xml
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -15,11 +15,13 @@
*/
-->
<resources>
+ <!-- Task View -->
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+ <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
<dimen name="overview_task_margin">16dp</dimen>
+ <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
<dimen name="overview_task_margin_grid">16dp</dimen>
- <dimen name="overview_grid_side_margin">64dp</dimen>
<dimen name="overview_grid_row_spacing">36dp</dimen>
<dimen name="overview_page_spacing">44dp</dimen>
- <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
- <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+ <dimen name="overview_grid_side_margin">64dp</dimen>
</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2b71768..3072a3e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,8 +15,6 @@
-->
<resources>
- <dimen name="task_thumbnail_icon_size">48dp</dimen>
- <dimen name="task_thumbnail_icon_drawable_size">48dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
<!-- For Launchers that want to override the default dialog corner radius -->
@@ -26,25 +24,26 @@
<dimen name="task_menu_corner_radius">22dp</dimen>
<dimen name="task_menu_item_corner_radius">4dp</dimen>
<dimen name="task_menu_spacing">2dp</dimen>
- <dimen name="task_menu_width_grid">234dp</dimen>
+ <dimen name="task_menu_width_grid">216dp</dimen>
<dimen name="task_menu_horizontal_padding">8dp</dimen>
<dimen name="overview_proactive_row_height">48dp</dimen>
<dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
<dimen name="overview_minimum_next_prev_size">50dp</dimen>
+
+ <!-- Task View -->
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+ <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
<dimen name="overview_task_margin">16dp</dimen>
- <dimen name="overview_task_margin_grid">0dp</dimen>
+ <dimen name="overview_page_spacing">16dp</dimen>
+
<item name="overview_max_scale" format="float" type="dimen">0.7</item>
+ <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
<!-- Overrideable in overlay that provides the Overview Actions. -->
+ <dimen name="overview_actions_top_margin">24dp</dimen>
<dimen name="overview_actions_height">48dp</dimen>
- <dimen name="overview_actions_button_spacing">32dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">28dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">28dp</dimen>
- <dimen name="overview_actions_margin_three_button">8dp</dimen>
- <dimen name="overview_actions_horizontal_margin">16dp</dimen>
-
- <dimen name="overview_page_spacing">16dp</dimen>
+ <dimen name="overview_actions_button_spacing">36dp</dimen>
<!-- These speeds are in dp/s -->
<dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f80deeb..81b0dd2 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -109,6 +109,8 @@
<string name="back_gesture_intro_title">Swipe to go back</string>
<!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=200] -->
<string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
+ <!-- Introduction subtitle for the Back gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=200] -->
+ <string name="back_gesture_spoken_intro_subtitle">To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen.</string>
<string name="home_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
<!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
@@ -123,6 +125,8 @@
<string name="home_gesture_intro_title">Swipe to go home</string>
<!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
<string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
+ <!-- Introduction subtitle for the Home gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
+ <string name="home_gesture_spoken_intro_subtitle">Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen.</string>
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
<string name="overview_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
@@ -138,6 +142,8 @@
<string name="overview_gesture_intro_title">Swipe to switch apps</string>
<!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
<string name="overview_gesture_intro_subtitle">To switch between apps, swipe up from the bottom of your screen, hold, then release.</string>
+ <!-- Introduction subtitle for the Overview gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_spoken_intro_subtitle">To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release.</string>
<!-- Title shown during interactive part of Assistant gesture tutorial. [CHAR LIMIT=30] -->
<string name="assistant_gesture_tutorial_playground_title" translatable="false">Tutorial: Assistant</string>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index fe24c4b..2239102 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -24,6 +24,9 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.NavigationMode.TWO_BUTTONS;
@@ -229,6 +232,28 @@
public void onScrollChanged(float progress) {
super.onScrollChanged(progress);
mDepthController.onOverlayScrollChanged(progress);
+ onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX);
+ }
+
+ @Override
+ public void onAllAppsTransition(float progress) {
+ super.onAllAppsTransition(progress);
+ onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX);
+ }
+
+ @Override
+ public void onWidgetsTransition(float progress) {
+ super.onWidgetsTransition(progress);
+ onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
+ }
+
+ private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
+ if (mTaskbarManager == null
+ || mTaskbarManager.getCurrentActivityContext() == null
+ || mTaskbarUIController == null) {
+ return;
+ }
+ mTaskbarUIController.onTaskbarInAppDisplayProgressUpdate(progress, flag);
}
@Override
@@ -296,7 +321,7 @@
new SplitSelectStateController(this, mHandler, getStateManager(),
getDepthController());
overviewPanel.init(mActionsView, controller);
- mActionsView.setDp(getDeviceProfile());
+ mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
mAppTransitionManager = new QuickstepTransitionManager(this);
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 36c9d13..9f35401 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -42,7 +42,6 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
-import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
@@ -105,7 +104,6 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.DynamicResource;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
@@ -195,9 +193,7 @@
private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
protected final BaseQuickstepLauncher mLauncher;
-
private final DragLayer mDragLayer;
- private final AlphaProperty mDragLayerAlpha;
final Handler mHandler;
@@ -241,11 +237,9 @@
public QuickstepTransitionManager(Context context) {
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mDragLayer = mLauncher.getDragLayer();
- mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
- mBackAnimationController = new LauncherBackAnimationController(
- mDeviceProfile, mLauncher, this);
+ mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
Resources res = mLauncher.getResources();
mContentScale = res.getFloat(R.dimen.content_scale);
@@ -1162,7 +1156,7 @@
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread());
mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
- SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
+ SystemUiProxy.INSTANCE.get(mLauncher).registerRemoteTransition(mLauncherOpenTransition);
}
if (mBackAnimationController != null) {
mBackAnimationController.registerBackCallbacks(mHandler);
@@ -1173,7 +1167,7 @@
unregisterRemoteAnimations();
unregisterRemoteTransitions();
mStartingWindowListener.setTransitionManager(null);
- SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null);
+ SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
}
private void unregisterRemoteAnimations() {
@@ -1197,7 +1191,7 @@
}
if (hasControlRemoteAppTransitionPermission()) {
if (mLauncherOpenTransition == null) return;
- SystemUiProxy.INSTANCE.getNoCreate().unregisterRemoteTransition(
+ SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
mLauncherOpenTransition);
mLauncherOpenTransition = null;
mWallpaperOpenTransitionRunner = null;
@@ -1360,7 +1354,7 @@
*/
private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS,
- RectF closingWindowStartRect) {
+ RectF closingWindowStartRect, float startWindowCornerRadius) {
FloatingIconView floatingIconView = null;
FloatingWidgetView floatingWidget = null;
RectF targetRect = new RectF();
@@ -1409,7 +1403,7 @@
final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
- windowTargetBounds) {
+ windowTargetBounds, startWindowCornerRadius) {
@Override
public void onUpdate(RectF currentRectF, float progress) {
finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress,
@@ -1427,7 +1421,7 @@
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
FloatingWidgetView finalFloatingWidget = floatingWidget;
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
- windowTargetBounds) {
+ windowTargetBounds, startWindowCornerRadius) {
@Override
public void onUpdate(RectF currentRectF, float progress) {
final float fallbackBackgroundAlpha =
@@ -1441,6 +1435,11 @@
}
};
anim.addOnUpdateListener(runner);
+ } else {
+ // If no floating icon or widget is present, animate the to the default window
+ // target rect.
+ anim.addOnUpdateListener(new SpringAnimRunner(
+ targets, targetRect, windowTargetBounds, startWindowCornerRadius));
}
// Use a fixed velocity to start the animation.
@@ -1582,7 +1581,8 @@
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
boolean fromUnlock,
- RectF startRect) {
+ RectF startRect,
+ float startWindowCornerRadius) {
AnimatorSet anim = null;
RectFSpringAnim rectFSpringAnim = null;
@@ -1614,7 +1614,8 @@
.getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
PointF velocity = new PointF(0, -velocityPxPerS);
rectFSpringAnim = getClosingWindowAnimators(
- anim, appTargets, launcherView, velocity, startRect);
+ anim, appTargets, launcherView, velocity, startRect,
+ startWindowCornerRadius);
if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
true /* animateOverviewScrim */, launcherView).getAnimators());
@@ -1713,7 +1714,8 @@
Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
appTargets, wallpaperTargets, mFromUnlock,
- new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx));
+ new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx),
+ QuickStepContract.getWindowCornerRadius(mLauncher));
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
result.setAnimation(pair.second, mLauncher);
@@ -1876,9 +1878,9 @@
private final Rect mTmpRect = new Rect();
SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect,
- Rect windowTargetBounds) {
+ Rect windowTargetBounds, float startWindowCornerRadius) {
mAppTargets = appTargets;
- mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+ mStartRadius = startWindowCornerRadius;
mEndRadius = Math.max(1, targetRect.width()) / 2f;
mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
mWindowTargetBounds.set(windowTargetBounds);
diff --git a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
index 6c4c601..9c3b881 100644
--- a/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
+++ b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
@@ -19,6 +19,7 @@
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import com.android.launcher3.LauncherSettings;
@@ -38,8 +39,8 @@
}
@Override
- public WorkspaceItemInfo makeWorkspaceItem() {
- WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem();
+ public WorkspaceItemInfo makeWorkspaceItem(Context context) {
+ WorkspaceItemInfo workspaceItemInfo = super.makeWorkspaceItem(context);
workspaceItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
workspaceItemInfo.status = WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON
| WorkspaceItemInfo.FLAG_RESTORE_STARTED
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85d9f01..05b8167 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -55,13 +55,13 @@
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.views.Snackbar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -409,11 +409,11 @@
@Nullable
@Override
public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
- ItemInfo itemInfo) {
+ ItemInfo itemInfo, View originalView) {
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
return null;
}
- return new PinPrediction(activity, itemInfo);
+ return new PinPrediction(activity, itemInfo, originalView);
}
private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
@@ -480,8 +480,8 @@
*
* @param matcher filter matching items that have been removed
*/
- public void onModelItemsRemoved(ItemInfoMatcher matcher) {
- if (mPredictedItems.removeIf(matcher::matchesInfo)) {
+ public void onModelItemsRemoved(Predicate<ItemInfo> matcher) {
+ if (mPredictedItems.removeIf(matcher)) {
fillGapsWithPrediction(true);
}
}
@@ -498,9 +498,9 @@
private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
- private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+ private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_pin, R.string.pin_prediction, target,
- itemInfo);
+ itemInfo, originalView);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 5c66944..9f3be69 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -62,6 +62,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FolderContainer;
@@ -116,7 +117,8 @@
@AnyThread
private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
int targetPredictor) {
- if (target != null) {
+ // TODO: remove the running test check when b/231648228 is fixed.
+ if (target != null && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
.setLaunchLocation(getContainer(locationInfo))
.build();
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 9c3daea..7e3ee7d 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -85,7 +85,7 @@
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
.map(ai -> {
app.getIconCache().getTitleAndIcon(ai, false);
- return ai.makeWorkspaceItem();
+ return ai.makeWorkspaceItem(context);
})
.findAny()
.orElseGet(() -> {
@@ -96,7 +96,7 @@
}
AppInfo ai = new AppInfo(context, lai, user);
app.getIconCache().getTitleAndIcon(ai, lai, false);
- return ai.makeWorkspaceItem();
+ return ai.makeWorkspaceItem(context);
});
if (itemInfo == null) {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 0e534f4..770dfb2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -460,7 +460,7 @@
AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
mReadCount++;
- return info.makeWorkspaceItem();
+ return info.makeWorkspaceItem(mAppState.getContext());
}
case ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutKey key = ShortcutKey.fromIntent(intent, user);
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index e489cb3..68ed682 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -40,6 +40,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.View;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -193,7 +194,7 @@
@MainThread
private SystemShortcut getShortcutForApp(String packageName, int userId,
- BaseDraggingActivity activity, ItemInfo info) {
+ BaseDraggingActivity activity, ItemInfo info, View originalView) {
Preconditions.assertUIThread();
// Work profile apps are not recognized by digital wellbeing.
if (userId != UserHandle.myUserId()) {
@@ -217,7 +218,7 @@
"getShortcutForApp [" + packageName + "]: action: '" + action.getTitle()
+ "'");
}
- return new RemoteActionShortcut(action, activity, info);
+ return new RemoteActionShortcut(action, activity, info, originalView);
}
}
@@ -378,8 +379,8 @@
* Shortcut factory for generating wellbeing action
*/
public static final SystemShortcut.Factory<BaseDraggingActivity> SHORTCUT_FACTORY =
- (activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
- .getShortcutForApp(
+ (activity, info, originalView) -> (info.getTargetComponent() == null) ? null
+ : INSTANCE.get(activity).getShortcutForApp(
info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
- activity, info);
+ activity, info, originalView);
}
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index cc0072e..4e59790 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -34,8 +34,9 @@
static SystemShortcut.Factory<BaseQuickstepLauncher> getSplitSelectShortcutByPosition(
SplitPositionOption position) {
- return (activity, itemInfo) -> new QuickstepSystemShortcut.SplitSelectSystemShortcut(
- activity, itemInfo, position);
+ return (activity, itemInfo, originalView) ->
+ new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo,
+ originalView, position);
}
class SplitSelectSystemShortcut extends SystemShortcut<BaseQuickstepLauncher> {
@@ -43,8 +44,8 @@
private final SplitPositionOption mPosition;
public SplitSelectSystemShortcut(BaseQuickstepLauncher launcher, ItemInfo itemInfo,
- SplitPositionOption position) {
- super(position.iconResId, position.textResId, launcher, itemInfo);
+ View originalView, SplitPositionOption position) {
+ super(position.iconResId, position.textResId, launcher, itemInfo, originalView);
mPosition = position;
}
@@ -69,7 +70,8 @@
RecentsView recentsView = mTarget.getOverviewPanel();
recentsView.initiateSplitSelect(
- new SplitSelectSource(view, new BitmapDrawable(bitmap), intent, mPosition));
+ new SplitSelectSource(mOriginalView, new BitmapDrawable(bitmap), intent,
+ mPosition));
}
}
diff --git a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
index bee8bb8..b47ef47 100644
--- a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
+++ b/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
@@ -16,6 +16,10 @@
package com.android.launcher3.proxy;
+import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
@@ -45,7 +49,7 @@
public StartActivityParams(Activity activity, int requestCode) {
this(activity.createPendingResult(requestCode, new Intent(),
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT), requestCode);
+ FLAG_ONE_SHOT | FLAG_UPDATE_CURRENT | FLAG_MUTABLE), requestCode);
}
public StartActivityParams(PendingIntent pendingIntent, int requestCode) {
diff --git a/quickstep/src/com/android/launcher3/search/SearchSessionManager.java b/quickstep/src/com/android/launcher3/search/SearchSessionManager.java
deleted file mode 100644
index da85793..0000000
--- a/quickstep/src/com/android/launcher3/search/SearchSessionManager.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.search;
-
-import android.app.search.SearchSession;
-import android.content.Context;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.R;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/** Manages an all apps search session. */
-public class SearchSessionManager implements ResourceBasedOverride {
-
- /** Entry state for the search session (e.g. from all apps). */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {ZERO_ALLAPPS, ZERO_QSB})
- public @interface ZeroEntryState {}
- public static final int ZERO_ALLAPPS = 1;
- public static final int ZERO_QSB = 2;
-
- /** Creates a {@link SearchSessionManager} instance. */
- public static SearchSessionManager newInstance(Context context) {
- return Overrides.getObject(
- SearchSessionManager.class, context, R.string.search_session_manager_class);
- }
-
- /** The current {@link SearchSession}. */
- @UiThread
- public void setSearchSession(SearchSession session) {}
-
- /** {@code true} if IME is shown. */
- public void setIsImeShown(boolean value) {}
-
- /** Returns {@code true} if IME is enabled. */
- public boolean getIsImeEnabled() {
- return false;
- }
-
- /** The current entry state for search. */
- public @ZeroEntryState int getEntryState() {
- return ZERO_ALLAPPS;
- }
-
- /**
- * When user enters all apps surface via tap on home widget, set the state to
- * {@code #ZERO_QSB}. When user exits, reset to {@code #ZERO_ALLAPPS}
- */
- public void setEntryState(@ZeroEntryState int state) {}
-
- /** This will be called before opening all apps, to prepare zero state suggestions. */
- public void prepareZeroState() {}
-
- /** Apply predicted items for the search zero state. */
- public void setZeroStatePredictedItems(List<ItemInfo> items) {}
-
- /** Returns {@code true} if the session is valid and should be enabled. */
- public boolean isValidSession() {
- return false;
- }
-
- /** Called when the search session is destroyed. */
- public void onDestroy() {}
-}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 3b02599..eda0823 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -53,6 +53,7 @@
public class DepthController implements StateHandler<LauncherState>,
BaseActivity.MultiWindowModeChangedListener {
+ private static final boolean OVERLAY_SCROLL_ENABLED = false;
public static final FloatProperty<DepthController> DEPTH =
new FloatProperty<DepthController>("depth") {
@Override
@@ -294,6 +295,9 @@
}
public void onOverlayScrollChanged(float progress) {
+ if (!OVERLAY_SCROLL_ENABLED) {
+ return;
+ }
// Add some padding to the progress, such we don't change the depth on the last frames of
// the animation. It's possible that a user flinging the feed quickly would scroll
// horizontally by accident, causing the device to enter client composition unnecessarily.
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
index cf56248..e2359c0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarUIController.java
@@ -39,4 +39,10 @@
protected void onDestroy() {
mLauncher.getHotseat().setIconsAlpha(1f);
}
+
+ @Override
+ /** Disable taskbar stashing in desktop environment. */
+ public boolean supportsVisualStashing() {
+ return false;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 793d987..ca30e72 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -19,14 +19,17 @@
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
import android.animation.Animator;
+import android.animation.AnimatorSet;
import android.annotation.ColorInt;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.TaskTransitionSpec;
import android.view.WindowManagerGlobal;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -43,6 +46,7 @@
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsAnimationCallbacks;
+import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
@@ -54,10 +58,22 @@
private static final String TAG = "TaskbarUIController";
+ public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0;
+ public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
+ public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
+ public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
+
+ private final SparseArray<Float> mTaskbarInAppDisplayProgress = new SparseArray<>(4);
+
private final BaseQuickstepLauncher mLauncher;
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
- this::onStashedInAppChanged;
+ dp -> {
+ onStashedInAppChanged(dp);
+ if (mControllers != null && mControllers.taskbarViewController != null) {
+ mControllers.taskbarViewController.onRotationChanged(dp);
+ }
+ };
// Initialized in init.
private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
@@ -65,15 +81,6 @@
private final TaskbarLauncherStateController
mTaskbarLauncherStateController = new TaskbarLauncherStateController();
- private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener =
- new DeviceProfile.OnDeviceProfileChangeListener() {
- @Override
- public void onDeviceProfileChanged(DeviceProfile dp) {
- mControllers.taskbarViewController.onRotationChanged(
- mLauncher.getDeviceProfile());
- }
- };
-
public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
}
@@ -93,7 +100,6 @@
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
- mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener);
}
@Override
@@ -102,9 +108,8 @@
onLauncherResumedOrPaused(false);
mTaskbarLauncherStateController.onDestroy();
- mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mLauncher.setTaskbarUIController(null);
- mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener);
+ mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
updateTaskTransitionSpec(true);
}
@@ -137,6 +142,24 @@
}
/**
+ * Adds the Launcher resume animator to the given animator set.
+ *
+ * This should be used to run a Launcher resume animation whose progress matches a
+ * swipe progress.
+ *
+ * @param placeholderDuration a placeholder duration to be used to ensure all full-length
+ * sub-animations are properly coordinated. This duration should not
+ * actually be used since this animation tracks a swipe progress.
+ */
+ protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) {
+ animation.play(onLauncherResumedOrPaused(
+ /* isResumed= */ true,
+ /* fromInit= */ false,
+ /* startAnimation= */ false,
+ placeholderDuration));
+ }
+
+ /**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
@@ -144,9 +167,19 @@
}
private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
+ onLauncherResumedOrPaused(
+ isResumed,
+ fromInit,
+ /* startAnimation= */ true,
+ QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
+ }
+
+ @Nullable
+ private Animator onLauncherResumedOrPaused(
+ boolean isResumed, boolean fromInit, boolean startAnimation, int duration) {
if (mKeyguardController.isScreenOff()) {
if (!isResumed) {
- return;
+ return null;
} else {
// Resuming implicitly means device unlocked
mKeyguardController.setScreenOn();
@@ -154,8 +187,7 @@
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
- mTaskbarLauncherStateController.applyState(
- fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
+ return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
}
/**
@@ -271,4 +303,85 @@
// gesture ends, start drawing taskbar's background again since launcher might stop drawing.
forceHideBackground(inProgress);
}
+
+ /**
+ * Animates Taskbar elements during a transition to a Launcher state that should use in-app
+ * layouts.
+ *
+ * @param progress [0, 1]
+ * 0 => use home layout
+ * 1 => use in-app layout
+ */
+ public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) {
+ if (mControllers == null) {
+ // This method can be called before init() is called.
+ return;
+ }
+ mTaskbarInAppDisplayProgress.put(progressIndex, progress);
+ if (!mControllers.taskbarStashController.isInApp()
+ && !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
+ // Only animate the nav buttons while home and not animating home, otherwise let
+ // the TaskbarViewController handle it.
+ mControllers.navbarButtonsViewController
+ .getTaskbarNavButtonTranslationYForInAppDisplay()
+ .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
+ * getInAppDisplayProgress());
+ }
+ }
+
+ /** Returns true iff any in-app display progress > 0. */
+ public boolean shouldUseInAppLayout() {
+ return getInAppDisplayProgress() > 0;
+ }
+
+ private float getInAppDisplayProgress(int index) {
+ if (!mTaskbarInAppDisplayProgress.contains(index)) {
+ mTaskbarInAppDisplayProgress.put(index, 0f);
+ }
+ return mTaskbarInAppDisplayProgress.get(index);
+ }
+
+ private float getInAppDisplayProgress() {
+ return Stream.of(
+ getInAppDisplayProgress(MINUS_ONE_PAGE_PROGRESS_INDEX),
+ getInAppDisplayProgress(ALL_APPS_PAGE_PROGRESS_INDEX),
+ getInAppDisplayProgress(WIDGETS_PAGE_PROGRESS_INDEX),
+ getInAppDisplayProgress(SYSUI_SURFACE_PROGRESS_INDEX))
+ .max(Float::compareTo)
+ .get();
+ }
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ super.dumpLogs(prefix, pw);
+
+ pw.println(String.format(
+ "%s\tmTaskbarOverrideBackgroundAlpha=%.2f",
+ prefix,
+ mTaskbarOverrideBackgroundAlpha.value));
+
+ pw.println(String.format("%s\tTaskbar in-app display progress:", prefix));
+ if (mControllers == null) {
+ pw.println(String.format("%s\t\tMissing mControllers", prefix));
+ } else {
+ pw.println(String.format(
+ "%s\t\tprogress at MINUS_ONE_PAGE_PROGRESS_INDEX=%.2f",
+ prefix,
+ getInAppDisplayProgress(MINUS_ONE_PAGE_PROGRESS_INDEX)));
+ pw.println(String.format(
+ "%s\t\tprogress at ALL_APPS_PAGE_PROGRESS_INDEX=%.2f",
+ prefix,
+ getInAppDisplayProgress(ALL_APPS_PAGE_PROGRESS_INDEX)));
+ pw.println(String.format(
+ "%s\t\tprogress at WIDGETS_PAGE_PROGRESS_INDEX=%.2f",
+ prefix,
+ getInAppDisplayProgress(WIDGETS_PAGE_PROGRESS_INDEX)));
+ pw.println(String.format(
+ "%s\t\tprogress at SYSUI_SURFACE_PROGRESS_INDEX=%.2f",
+ prefix,
+ getInAppDisplayProgress(SYSUI_SURFACE_PROGRESS_INDEX)));
+ }
+
+ mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 175a1d9..cbdbdb9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
+import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -50,6 +52,7 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.PaintDrawable;
import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
import android.util.Property;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -126,11 +129,13 @@
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
this::updateNavButtonTranslationY);
+ private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
+ this::updateNavButtonTranslationY);
private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
this::updateNavButtonTranslationY);
- // Only applies to mTaskbarNavButtonTranslationY
- private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
- this::updateNavButtonTranslationY);
+ // Used for System UI state updates that should translate the nav button for in-app display.
+ private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
+ this::updateNavButtonInAppDisplayProgressForSysui);
private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
this::updateNavButtonDarkIntensity);
private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
@@ -155,6 +160,7 @@
private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer =
this::onComputeInsetsForSeparateWindow;
+ private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
mContext = context;
@@ -173,7 +179,6 @@
public void init(TaskbarControllers controllers) {
mControllers = controllers;
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
- mNavButtonTranslationYMultiplier.value = 1;
boolean isThreeButtonNav = mContext.isThreeButtonNav();
mIsImeRenderingNavButtons =
@@ -205,12 +210,12 @@
// Make sure to remove nav bar buttons translation when notification shade is expanded or
// IME is showing (add separate translation for IME).
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
- mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
+ mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
- 0, 1));
+ 1, 0));
// Center nav buttons in new height for IME.
float transForIme = (mContext.getDeviceProfile().taskbarSize
- - mContext.getTaskbarHeightForIme()) / 2f;
+ - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
// For gesture nav, nav buttons only show for IME anyway so keep them translated down.
float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
@@ -295,6 +300,8 @@
navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
navButtonsLayoutParams.gravity = Gravity.CENTER;
mNavButtonContainer.requestLayout();
+
+ mHomeButton.setOnLongClickListener(null);
}
// Animate taskbar background when either..
@@ -386,8 +393,7 @@
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
-
- // home and recents buttons
+ // home button
mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
navButtonController, R.id.home);
mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
@@ -397,8 +403,21 @@
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
(flags & FLAG_DISABLE_HOME) == 0));
+
+ // Recents button
View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
navContainer, navButtonController, R.id.recent_apps);
+ mHitboxExtender.init(recentsButton, mNavButtonsView, mContext.getDeviceProfile(),
+ () -> {
+ float[] recentsCoords = new float[2];
+ getDescendantCoordRelativeToAncestor(recentsButton, mNavButtonsView,
+ recentsCoords, false);
+ return recentsCoords;
+ }, new Handler());
+ recentsButton.setOnClickListener(v -> {
+ navButtonController.onButtonClick(BUTTON_RECENTS);
+ mHitboxExtender.onRecentsButtonClicked();
+ });
mPropertyHolders.add(new StatePropertyHolder(recentsButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
&& !mContext.isNavBarKidsModeActive()));
@@ -502,6 +521,9 @@
View button = mAllButtons.get(i);
if (button.getVisibility() == View.VISIBLE) {
parent.getDescendantRectRelativeToSelf(button, mTempRect);
+ if (mHitboxExtender.extendedHitboxEnabled()) {
+ mTempRect.bottom += mContext.mDeviceProfile.getTaskbarOffsetY();
+ }
outRegion.op(mTempRect, Op.UNION);
}
}
@@ -526,6 +548,11 @@
return mTaskbarNavButtonTranslationY;
}
+ /** Use to set the translationY for the all nav+contextual buttons when in Launcher */
+ public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
+ return mTaskbarNavButtonTranslationYForInAppDisplay;
+ }
+
/** Use to set the dark intensity for the all nav+contextual buttons */
public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
return mTaskbarNavButtonDarkIntensity;
@@ -554,11 +581,26 @@
}
}
+ private void updateNavButtonInAppDisplayProgressForSysui() {
+ TaskbarUIController uiController = mControllers.uiController;
+ if (uiController instanceof LauncherTaskbarUIController) {
+ ((LauncherTaskbarUIController) uiController).onTaskbarInAppDisplayProgressUpdate(
+ mNavButtonInAppDisplayProgressForSysui.value, SYSUI_SURFACE_PROGRESS_INDEX);
+ }
+ }
+
private void updateNavButtonTranslationY() {
- float normalTranslationY = mTaskbarNavButtonTranslationY.value
- * mNavButtonTranslationYMultiplier.value;
- float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value;
- mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY);
+ final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
+ final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
+ TaskbarUIController uiController = mControllers.uiController;
+ final float inAppDisplayAdjustmentTranslationY =
+ (uiController instanceof LauncherTaskbarUIController
+ && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
+ ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
+
+ mNavButtonsView.setTranslationY(normalTranslationY
+ + imeAdjustmentTranslationY
+ + inAppDisplayAdjustmentTranslationY);
}
private void updateNavButtonDarkIntensity() {
@@ -711,6 +753,17 @@
return str.toString();
}
+ public TouchController getTouchController() {
+ return mHitboxExtender;
+ }
+
+ /**
+ * @param alignment 0 -> Taskbar, 1 -> Workspace
+ */
+ public void updateTaskbarAlignment(float alignment) {
+ mHitboxExtender.onAnimationProgressToOverview(alignment);
+ }
+
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/RecentsHitboxExtender.java b/quickstep/src/com/android/launcher3/taskbar/RecentsHitboxExtender.java
new file mode 100644
index 0000000..4651570
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/RecentsHitboxExtender.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar;
+
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.util.TouchController;
+
+import java.util.function.Supplier;
+
+/**
+ * Extends the Recents touch area during the taskbar to overview animation
+ * to give user some error room when trying to quickly double tap recents button since it moves.
+ *
+ * Listens for icon alignment as our indication for the animation.
+ */
+public class RecentsHitboxExtender implements TouchController {
+
+ private static final int RECENTS_HITBOX_TIMEOUT_MS = 500;
+
+ private View mRecentsButton;
+ private View mRecentsParent;
+ private DeviceProfile mDeviceProfile;
+ private Supplier<float[]> mParentCoordSupplier;
+ private TouchDelegate mRecentsTouchDelegate;
+ /**
+ * Will be true while the animation from taskbar to overview is occurring.
+ * Lifecycle of this variable slightly extends past the animation by
+ * {@link #RECENTS_HITBOX_TIMEOUT_MS}, so can use this variable as a proxy for if
+ * the current hitbox is extended or not.
+ */
+ private boolean mAnimatingFromTaskbarToOverview;
+ private float mLastIconAlignment;
+ private final Rect mRecentsHitBox = new Rect();
+ private boolean mRecentsButtonClicked;
+ private Handler mHandler;
+ private final Runnable mRecentsHitboxResetRunnable = this::reset;
+
+ public void init(View recentsButton, View recentsParent, DeviceProfile deviceProfile,
+ Supplier<float[]> parentCoordSupplier, Handler handler) {
+ mRecentsButton = recentsButton;
+ mRecentsParent = recentsParent;
+ mDeviceProfile = deviceProfile;
+ mParentCoordSupplier = parentCoordSupplier;
+ mHandler = handler;
+ }
+
+ public void onRecentsButtonClicked() {
+ mRecentsButtonClicked = true;
+ }
+
+ /**
+ * @param progress 0 -> Taskbar, 1 -> Overview
+ */
+ public void onAnimationProgressToOverview(float progress) {
+ if (progress == 1 || progress == 0) {
+ // Done w/ animation
+ mLastIconAlignment = progress;
+ if (mAnimatingFromTaskbarToOverview) {
+ if (progress == 1) {
+ // Finished animation to workspace, remove the touch delegate shortly
+ mHandler.postDelayed(mRecentsHitboxResetRunnable, RECENTS_HITBOX_TIMEOUT_MS);
+ return;
+ } else {
+ // Went back to taskbar, reset immediately
+ mHandler.removeCallbacks(mRecentsHitboxResetRunnable);
+ reset();
+ }
+ }
+ }
+
+ if (mAnimatingFromTaskbarToOverview) {
+ return;
+ }
+
+ if (progress > 0 && mLastIconAlignment == 0 && mRecentsButtonClicked) {
+ // Starting animation, previously we were showing taskbar
+ mAnimatingFromTaskbarToOverview = true;
+ float[] recentsCoords = mParentCoordSupplier.get();
+ int x = (int) recentsCoords[0];
+ int y = (int) (recentsCoords[1]);
+ // Extend hitbox vertically by the offset amount from mDeviceProfile.getTaskbarOffsetY()
+ mRecentsHitBox.set(x, y,
+ x + mRecentsButton.getWidth(),
+ y + mRecentsButton.getHeight() + mDeviceProfile.getTaskbarOffsetY()
+ );
+ mRecentsTouchDelegate = new TouchDelegate(mRecentsHitBox, mRecentsButton);
+ mRecentsParent.setTouchDelegate(mRecentsTouchDelegate);
+ }
+ }
+
+ private void reset() {
+ mAnimatingFromTaskbarToOverview = false;
+ mRecentsButton.setTouchDelegate(null);
+ mRecentsHitBox.setEmpty();
+ mRecentsButtonClicked = false;
+ }
+
+ /**
+ * @return {@code true} if the bounds for recents touches are currently extended
+ */
+ public boolean extendedHitboxEnabled() {
+ return mAnimatingFromTaskbarToOverview;
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mRecentsTouchDelegate.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ return mRecentsHitBox.contains((int)ev.getX(), (int)ev.getY());
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index a9ae7bd..b797807 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -176,7 +176,8 @@
return revealAnim;
}
- public void onIsStashed(boolean isStashed) {
+ /** Called when taskbar is stashed or unstashed. */
+ public void onIsStashedChanged(boolean isStashed) {
mRegionSamplingHelper.setWindowVisible(isStashed);
if (isStashed) {
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a22398d..ed1001c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,11 +27,9 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_SIZE;
import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -39,7 +37,6 @@
import android.content.pm.ActivityInfo.Config;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Process;
@@ -64,6 +61,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -88,7 +87,6 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
@@ -113,7 +111,6 @@
private final WindowManager mWindowManager;
private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
- private final int mTaskbarHeightForIme;
private WindowManager.LayoutParams mWindowLayoutParams;
private boolean mIsFullscreen;
// The size we should return to when we call setTaskbarWindowFullscreen(false)
@@ -154,7 +151,15 @@
Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
updateIconSize(resources);
- mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
+
+ // Get display and corners first, as views might use them in constructor.
+ Display display = windowContext.getDisplay();
+ Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
+ ? windowContext.getApplicationContext()
+ : windowContext.getApplicationContext().createDisplayContext(display);
+ mWindowManager = c.getSystemService(WindowManager.class);
+ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
// Inflate views.
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
@@ -164,14 +169,6 @@
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
- Display display = windowContext.getDisplay();
- Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
- ? windowContext.getApplicationContext()
- : windowContext.getApplicationContext().createDisplayContext(display);
- mWindowManager = c.getSystemService(WindowManager.class);
- mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
- mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
-
mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
// Construct controllers.
@@ -201,29 +198,14 @@
new TaskbarAutohideSuspendController(this),
new TaskbarPopupController(this),
new TaskbarForceVisibleImmersiveController(this),
- new TaskbarAllAppsController(this));
+ new TaskbarAllAppsController(this),
+ new TaskbarInsetsController(this));
}
- public void init(TaskbarSharedState sharedState) {
+ public void init(@NonNull TaskbarSharedState sharedState) {
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = createDefaultWindowLayoutParams();
- WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
- wmWrapper.setProvidesInsetsTypes(
- mWindowLayoutParams,
- new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
- );
- // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
- // the IME is showing
- mWindowLayoutParams.providedInternalImeInsets = new Insets[ITYPE_SIZE];
- final Insets reducingSize = Insets.of(0,
- getDefaultTaskbarWindowHeight() - mTaskbarHeightForIme, 0, 0);
- mWindowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize;
- mWindowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] =
- reducingSize;
-
- mWindowLayoutParams.insetsRoundedCornerFrame = true;
-
// Initialize controllers after all are constructed.
mControllers.init(sharedState);
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
@@ -303,6 +285,10 @@
return mRightCorner == null ? 0 : mRightCorner.getRadius();
}
+ public WindowManager.LayoutParams getWindowLayoutParams() {
+ return mWindowLayoutParams;
+ }
+
@Override
public TaskbarDragLayer getDragLayer() {
return mDragLayer;
@@ -379,6 +365,14 @@
folderBuilder.clearHotseat();
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
.setFolder(folderBuilder));
+ } else if (oldContainer.hasAllAppsContainer()) {
+ itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder()
+ .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+ } else if (oldContainer.hasPredictionContainer()) {
+ itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setPredictionContainer(oldContainer.getPredictionContainer().toBuilder()
+ .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
}
}
@@ -568,14 +562,7 @@
}
}
mWindowLayoutParams.height = height;
- final Insets reducingSize =
- Insets.of(0, height - mTaskbarHeightForIme, 0, 0);
- if (mWindowLayoutParams.providedInternalImeInsets == null) {
- mWindowLayoutParams.providedInternalImeInsets = new Insets[ITYPE_SIZE];
- }
- mWindowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize;
- mWindowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] =
- reducingSize;
+ mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
@@ -587,13 +574,6 @@
}
/**
- * Returns the bottom insets taskbar provides to the IME when IME is visible.
- */
- public int getTaskbarHeightForIme() {
- return mTaskbarHeightForIme;
- }
-
- /**
* Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
* window.
*/
@@ -702,6 +682,7 @@
}
} else if (tag instanceof AppInfo) {
startItemInfoActivity((AppInfo) tag);
+ mControllers.uiController.onTaskbarIconLaunched((AppInfo) tag);
} else {
Log.e(TAG, "Unknown type clicked: " + tag);
}
@@ -757,6 +738,45 @@
}
/**
+ * Displays a single frame of the Launcher start from SUW animation.
+ *
+ * This animation is a combination of the Launcher resume animation, which animates the hotseat
+ * icons into position, the Taskbar unstash to hotseat animation, which animates the Taskbar
+ * stash bar into the hotseat icons, and an override to prevent showing the Taskbar all apps
+ * button.
+ *
+ * This should be used to run a Taskbar unstash to hotseat animation whose progress matches a
+ * swipe progress.
+ *
+ * @param duration a placeholder duration to be used to ensure all full-length
+ * sub-animations are properly coordinated. This duration should not actually
+ * be used since this animation tracks a swipe progress.
+ */
+ protected AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) {
+ AnimatorSet fullAnimation = new AnimatorSet();
+ fullAnimation.setDuration(duration);
+
+ TaskbarUIController uiController = mControllers.uiController;
+ if (uiController instanceof LauncherTaskbarUIController) {
+ ((LauncherTaskbarUIController) uiController).addLauncherResumeAnimation(
+ fullAnimation, duration);
+ }
+ mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
+
+ if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
+ ValueAnimator alphaOverride = ValueAnimator.ofFloat(0, 1);
+ alphaOverride.setDuration(duration);
+ alphaOverride.addUpdateListener(a -> {
+ // Override the alpha updates in the icon alignment animation.
+ mControllers.taskbarViewController.getAllAppsButtonView().setAlpha(0);
+ });
+ fullAnimation.play(alphaOverride);
+ }
+
+ return AnimatorPlaybackController.wrap(fullAnimation, duration);
+ }
+
+ /**
* Called when we determine the touchable region.
*
* @param exclude {@code true} then the magnification region computation will omit the window.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
new file mode 100644
index 0000000..1177bdb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Path
+import com.android.launcher3.R
+
+/**
+ * Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners.
+ */
+class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
+
+ val paint: Paint = Paint()
+ var backgroundHeight = context.deviceProfile.taskbarSize.toFloat()
+
+ private val leftCornerRadius = context.leftCornerRadius.toFloat()
+ private val rightCornerRadius = context.rightCornerRadius.toFloat()
+ private val invertedLeftCornerPath: Path = Path()
+ private val invertedRightCornerPath: Path = Path()
+
+ init {
+ paint.color = context.getColor(R.color.taskbar_background)
+ paint.flags = Paint.ANTI_ALIAS_FLAG
+ paint.style = Paint.Style.FILL
+
+ // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
+ // square, and then subtract out a circle from the appropriate corner.
+ val square = Path()
+ square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW)
+ val circle = Path()
+ circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW)
+ invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)
+ square.reset()
+ square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW)
+ circle.reset()
+ circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW)
+ invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
+ }
+
+ /**
+ * Draws the background with the given paint and height, on the provided canvas.
+ */
+ fun draw(canvas: Canvas) {
+ canvas.save()
+ canvas.translate(0f, canvas.height - backgroundHeight)
+
+ // Draw the background behind taskbar content.
+ canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
+
+ // Draw the inverted rounded corners above the taskbar.
+ canvas.translate(0f, -leftCornerRadius)
+ canvas.drawPath(invertedLeftCornerPath, paint)
+ canvas.translate(0f, leftCornerRadius)
+ canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+ canvas.drawPath(invertedRightCornerPath, paint)
+
+ canvas.restore()
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index a3586396..449e0a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -51,6 +51,7 @@
public final TaskbarPopupController taskbarPopupController;
public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
public final TaskbarAllAppsController taskbarAllAppsController;
+ public final TaskbarInsetsController taskbarInsetsController;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@@ -60,6 +61,8 @@
private boolean mAreAllControllersInitialized;
private final List<Runnable> mPostInitCallbacks = new ArrayList<>();
+ @Nullable private TaskbarSharedState mSharedState = null;
+
public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
TaskbarDragController taskbarDragController,
TaskbarNavButtonController navButtonController,
@@ -76,7 +79,8 @@
TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
TaskbarPopupController taskbarPopupController,
TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
- TaskbarAllAppsController taskbarAllAppsController) {
+ TaskbarAllAppsController taskbarAllAppsController,
+ TaskbarInsetsController taskbarInsetsController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -94,6 +98,7 @@
this.taskbarPopupController = taskbarPopupController;
this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
this.taskbarAllAppsController = taskbarAllAppsController;
+ this.taskbarInsetsController = taskbarInsetsController;
}
/**
@@ -101,8 +106,9 @@
* TaskbarControllers instance, but should be careful to only access things that were created
* in constructors for now, as some controllers may still be waiting for init().
*/
- public void init(TaskbarSharedState sharedState) {
+ public void init(@NonNull TaskbarSharedState sharedState) {
mAreAllControllersInitialized = false;
+ mSharedState = sharedState;
taskbarDragController.init(this);
navbarButtonsViewController.init(this);
@@ -113,19 +119,20 @@
taskbarUnfoldAnimationController.init(this);
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
- taskbarStashController.init(this, sharedState);
+ taskbarStashController.init(this, sharedState.setupUIVisible);
taskbarEduController.init(this);
taskbarPopupController.init(this);
taskbarForceVisibleImmersiveController.init(this);
- taskbarAllAppsController.init(this, sharedState);
+ taskbarAllAppsController.init(this, sharedState.allAppsVisible);
navButtonController.init(this);
+ taskbarInsetsController.init(this);
mControllersToLog = new LoggableTaskbarController[] {
taskbarDragController, navButtonController, navbarButtonsViewController,
taskbarDragLayerController, taskbarScrimViewController, taskbarViewController,
taskbarUnfoldAnimationController, taskbarKeyguardController,
stashedHandleViewController, taskbarStashController, taskbarEduController,
- taskbarAutohideSuspendController, taskbarPopupController
+ taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController
};
mAreAllControllersInitialized = true;
@@ -135,6 +142,12 @@
mPostInitCallbacks.clear();
}
+ @Nullable
+ public TaskbarSharedState getSharedState() {
+ // This should only be null if called before init() and after destroy().
+ return mSharedState;
+ }
+
public void onConfigurationChanged(@Config int configChanges) {
navbarButtonsViewController.onConfigurationChanged(configChanges);
}
@@ -143,6 +156,8 @@
* Cleans up all controllers.
*/
public void onDestroy() {
+ mSharedState = null;
+
navbarButtonsViewController.onDestroy();
uiController.onDestroy();
rotationButtonController.onDestroy();
@@ -156,6 +171,7 @@
taskbarForceVisibleImmersiveController.onDestroy();
taskbarAllAppsController.onDestroy();
navButtonController.onDestroy();
+ taskbarInsetsController.onDestroy();
mControllersToLog = null;
}
@@ -182,9 +198,12 @@
return;
}
+ pw.println(String.format(
+ "%s\tmAreAllControllersInitialized=%b", prefix, mAreAllControllersInitialized));
for (LoggableTaskbarController controller : mControllersToLog) {
controller.dumpLogs(prefix + "\t", pw);
}
+ uiController.dumpLogs(prefix + "\t", pw);
rotationButtonController.dumpLogs(prefix + "\t", pw);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 9ba4a65..c522888 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -73,6 +73,7 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
+import java.util.function.Predicate;
/**
* Handles long click on Taskbar items to start a system drag and drop operation.
@@ -439,12 +440,12 @@
target = taskbarViewController.getAllAppsButtonView();
} else if (item.container >= 0) {
// Since folders close when the drag starts, target the folder icon instead.
- ItemInfoMatcher matcher = ItemInfoMatcher.forFolderMatch(
+ Predicate<ItemInfo> matcher = ItemInfoMatcher.forFolderMatch(
ItemInfoMatcher.ofItemIds(IntSet.wrap(item.id)));
target = taskbarViewController.getFirstIconMatch(matcher);
} else if (item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
// Find first icon with same package/user as the deep shortcut.
- ItemInfoMatcher packageUserMatcher = ItemInfoMatcher.ofPackages(
+ Predicate<ItemInfo> packageUserMatcher = ItemInfoMatcher.ofPackages(
Collections.singleton(item.getTargetPackage()), item.user);
target = taskbarViewController.getFirstIconMatch(packageUserMatcher);
}
@@ -477,7 +478,6 @@
tx.setScale(dragSurface, scale, scale);
tx.setAlpha(dragSurface, alpha);
tx.apply();
- tx.close();
}
});
mReturnAnimator.addListener(new AnimatorListenerAdapter() {
@@ -498,6 +498,7 @@
}
private void cleanUpSurface() {
+ tx.close();
maybeOnDragEnd();
// Synchronize removing the drag surface with the next draw after calling
// maybeOnDragEnd()
@@ -508,7 +509,6 @@
syncer.addToSync(syncId, viewRoot.getView());
syncer.addTransactionToSync(syncId, transaction);
syncer.markSyncReady(syncId);
-
mReturnAnimator = null;
}
});
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 4a80665..c1a6185 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -20,8 +20,6 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -31,7 +29,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.R;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
@@ -44,13 +41,11 @@
*/
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
- private final Paint mTaskbarBackgroundPaint;
- private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
+ private final TaskbarBackgroundRenderer mBackgroundRenderer;
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
// Initialized in init.
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
- private float mLeftCornerRadius, mRightCornerRadius;
private float mTaskbarBackgroundOffset;
@@ -70,35 +65,13 @@
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, 1 /* alphaChannelCount */);
- mTaskbarBackgroundPaint = new Paint();
- mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
- mTaskbarBackgroundPaint.setAlpha(0);
- mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
- mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
-
- // Will be set in init(), but this ensures they are always non-null.
- mInvertedLeftCornerPath = new Path();
- mInvertedRightCornerPath = new Path();
+ mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
+ mBackgroundRenderer.getPaint().setAlpha(0);
}
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
- // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
- // square, and then subtracting out a circle from the appropriate corner.
- mLeftCornerRadius = mActivity.getLeftCornerRadius();
- mRightCornerRadius = mActivity.getRightCornerRadius();
- Path square = new Path();
- square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
- Path circle = new Path();
- circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
- mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
- square.reset();
- square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
- circle.reset();
- circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
- mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-
recreateControllers();
}
@@ -110,7 +83,6 @@
private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
if (mControllerCallbacks != null) {
mControllerCallbacks.updateInsetsTouchability(insetsInfo);
- mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
}
}
@@ -151,20 +123,8 @@
protected void dispatchDraw(Canvas canvas) {
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
* (1f - mTaskbarBackgroundOffset);
- canvas.save();
- canvas.translate(0, canvas.getHeight() - backgroundHeight);
-
- // Draw the background behind taskbar content.
- canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
-
- // Draw the inverted rounded corners above the taskbar.
- canvas.translate(0, -mLeftCornerRadius);
- canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
- canvas.translate(0, mLeftCornerRadius);
- canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
- canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
-
- canvas.restore();
+ mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
+ mBackgroundRenderer.draw(canvas);
super.dispatchDraw(canvas);
}
@@ -173,7 +133,7 @@
* @param alpha 0 is fully transparent, 1 is fully opaque.
*/
protected void setTaskbarBackgroundAlpha(float alpha) {
- mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
+ mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
invalidate();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index c7330d3..99c59a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,18 +15,10 @@
*/
package com.android.launcher3.taskbar;
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
-import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
import android.content.res.Resources;
import android.graphics.Rect;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
@@ -168,47 +160,7 @@
* @see InsetsInfo#setTouchableInsets(int)
*/
public void updateInsetsTouchability(InsetsInfo insetsInfo) {
- insetsInfo.touchableRegion.setEmpty();
- // Always have nav buttons be touchable
- mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
- mTaskbarDragLayer, insetsInfo.touchableRegion);
- boolean insetsIsTouchableRegion = true;
-
- if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mControllers.navbarButtonsViewController.isImeVisible()) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (!mControllers.uiController.isTaskbarTouchable()) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mControllers.taskbarDragController.isSystemDragInProgress()) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (AbstractFloatingView.getOpenView(mActivity, TYPE_TASKBAR_ALL_APPS) != null) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mControllers.taskbarViewController.areIconsVisible()
- || AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null
- || mActivity.isNavBarKidsModeActive()) {
- // Taskbar has some touchable elements, take over the full taskbar area
- insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
- ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
- insetsIsTouchableRegion = false;
- } else {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- }
- mActivity.excludeFromMagnificationRegion(insetsIsTouchableRegion);
- }
-
- /**
- * Called to update the {@link InsetsInfo#contentInsets}. This is reported to apps but our
- * internal launcher will ignore these insets.
- */
- public void updateContentInsets(Rect outContentInsets) {
- int contentHeight = mControllers.taskbarStashController
- .getContentHeightToReportToApps();
- outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
+ mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo);
}
/**
@@ -230,7 +182,8 @@
*/
public TouchController[] getTouchControllers() {
return new TouchController[]{mActivity.getDragController(),
- mControllers.taskbarForceVisibleImmersiveController};
+ mControllers.taskbarForceVisibleImmersiveController,
+ mControllers.navbarButtonsViewController.getTouchController()};
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
new file mode 100644
index 0000000..9870a2e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar
+
+import android.graphics.Insets
+import android.view.WindowManager
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
+import com.android.launcher3.R
+import com.android.launcher3.anim.AlphaUpdateListener
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.quickstep.KtR
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo
+import com.android.systemui.shared.system.WindowManagerWrapper
+import com.android.systemui.shared.system.WindowManagerWrapper.*
+import java.io.PrintWriter
+
+/**
+ * Handles the insets that Taskbar provides to underlying apps and the IME.
+ */
+class TaskbarInsetsController(val context: TaskbarActivityContext): LoggableTaskbarController {
+
+ /** The bottom insets taskbar provides to the IME when IME is visible. */
+ val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(
+ KtR.dimen.taskbar_ime_size)
+
+ // Initialized in init.
+ private lateinit var controllers: TaskbarControllers
+ private lateinit var windowLayoutParams: WindowManager.LayoutParams
+
+ fun init(controllers: TaskbarControllers) {
+ this.controllers = controllers
+ windowLayoutParams = context.windowLayoutParams
+
+ val wmWrapper: WindowManagerWrapper = getInstance()
+ wmWrapper.setProvidesInsetsTypes(
+ windowLayoutParams,
+ intArrayOf(
+ ITYPE_EXTRA_NAVIGATION_BAR,
+ ITYPE_BOTTOM_TAPPABLE_ELEMENT
+ )
+ )
+
+ windowLayoutParams.providedInternalInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
+ windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
+
+ onTaskbarWindowHeightOrInsetsChanged()
+
+ windowLayoutParams.insetsRoundedCornerFrame = true
+ }
+
+ fun onDestroy() {}
+
+ fun onTaskbarWindowHeightOrInsetsChanged() {
+ var reducingSize = getReducingInsetsForTaskbarInsetsHeight(
+ controllers.taskbarStashController.contentHeightToReportToApps)
+ windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
+ reducingSize = getReducingInsetsForTaskbarInsetsHeight(
+ controllers.taskbarStashController.tappableHeightToReportToApps)
+ windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
+
+ reducingSize = getReducingInsetsForTaskbarInsetsHeight(taskbarHeightForIme)
+ windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
+ windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
+ }
+
+ /**
+ * WindowLayoutParams.providedInternal*Insets expects Insets that subtract from the window frame
+ * height (i.e. WindowLayoutParams#height). So for Taskbar to report bottom insets to apps, it
+ * actually provides insets from the top of its window frame.
+ * @param height The number of pixels from the bottom of the screen that Taskbar insets.
+ */
+ private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets {
+ return Insets.of(0, windowLayoutParams.height - height, 0, 0)
+ }
+
+ /**
+ * Called to update the touchable insets.
+ * @see InsetsInfo.setTouchableInsets
+ */
+ fun updateInsetsTouchability(insetsInfo: InsetsInfo) {
+ insetsInfo.touchableRegion.setEmpty()
+ // Always have nav buttons be touchable
+ controllers.navbarButtonsViewController.addVisibleButtonsRegion(
+ context.dragLayer, insetsInfo.touchableRegion
+ )
+ var insetsIsTouchableRegion = true
+ if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ } else if (controllers.navbarButtonsViewController.isImeVisible) {
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ } else if (!controllers.uiController.isTaskbarTouchable) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ } else if (controllers.taskbarDragController.isSystemDragInProgress) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_ALL_APPS)) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ } else if (controllers.taskbarViewController.areIconsVisible()
+ || AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL)
+ || context.isNavBarKidsModeActive
+ ) {
+ // Taskbar has some touchable elements, take over the full taskbar area
+ insetsInfo.setTouchableInsets(
+ if (context.isTaskbarWindowFullscreen) {
+ InsetsInfo.TOUCHABLE_INSETS_FRAME
+ } else {
+ InsetsInfo.TOUCHABLE_INSETS_CONTENT
+ }
+ )
+ insetsIsTouchableRegion = false
+ } else {
+ insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+ }
+ context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
+ }
+
+ override fun dumpLogs(prefix: String, pw: PrintWriter) {
+ pw.println(prefix + "TaskbarInsetsController:")
+ pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
+ pw.println("$prefix\tprovidedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
+ "${windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
+ pw.println("$prefix\tprovidedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
+ "${windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}")
+ pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
+ "${windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
+ pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
+ "${windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}")
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 36e6420..052c695 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -42,7 +42,9 @@
import com.android.systemui.animation.ViewRootSync;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -370,6 +372,7 @@
// If we're already animating to the value, just leave it be instead of restarting it.
if (!mIconAlignmentForLauncherState.isAnimatingToValue(toAlignment)) {
+ mIconAlignmentForLauncherState.finishAnimation();
animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
.setDuration(duration));
}
@@ -425,6 +428,7 @@
// Switch taskbar and hotseat in last frame
setTaskbarViewVisible(alignment < 1);
+ mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
}
private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
@@ -476,4 +480,48 @@
controller.applyState();
}
}
+
+ private static String getStateString(int flags) {
+ StringJoiner str = new StringJoiner("|");
+ str.add((flags & FLAG_RESUMED) != 0 ? "FLAG_RESUMED" : "");
+ str.add((flags & FLAG_RECENTS_ANIMATION_RUNNING) != 0
+ ? "FLAG_RECENTS_ANIMATION_RUNNING" : "");
+ str.add((flags & FLAG_TRANSITION_STATE_RUNNING) != 0
+ ? "FLAG_TRANSITION_STATE_RUNNING" : "");
+ return str.toString();
+ }
+
+ protected void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarLauncherStateController:");
+
+ pw.println(String.format(
+ "%s\tmIconAlignmentForResumedState=%.2f",
+ prefix,
+ mIconAlignmentForResumedState.value));
+ pw.println(String.format(
+ "%s\tmIconAlignmentForGestureState=%.2f",
+ prefix,
+ mIconAlignmentForGestureState.value));
+ pw.println(String.format(
+ "%s\tmIconAlignmentForLauncherState=%.2f",
+ prefix,
+ mIconAlignmentForLauncherState.value));
+ pw.println(String.format(
+ "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
+ pw.println(String.format(
+ "%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue()));
+ pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState)));
+ pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
+ pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
+ pw.println(String.format(
+ "%s\tmIsAnimatingToLauncherViaGesture=%b",
+ prefix,
+ mIsAnimatingToLauncherViaGesture));
+ pw.println(String.format(
+ "%s\tmIsAnimatingToLauncherViaResume=%b",
+ prefix,
+ mIsAnimatingToLauncherViaResume));
+ pw.println(String.format(
+ "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8e31a74..ef7bab9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import android.content.ComponentCallbacks;
@@ -38,10 +39,10 @@
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.RecentsActivity;
@@ -55,7 +56,7 @@
/**
* Class to manage taskbar lifecycle
*/
-public class TaskbarManager implements DisplayController.DisplayInfoChangeListener {
+public class TaskbarManager {
private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
@@ -91,8 +92,15 @@
* navigation mode, that callback gets called too soon, before it's internal navigation mode
* reflects the current one.
* DisplayController's callback is delayed enough to get the correct nav mode value
+ *
+ * We also use density change here because DeviceProfile has had a chance to update it's state
+ * whereas density for component callbacks registered in this class don't update DeviceProfile.
+ * Confused? Me too. Make it less confusing (TODO: b/227669780)
+ *
+ * Flags used with {@link #mDispInfoChangeListener}
*/
- private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE;
+ private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE | CHANGE_DENSITY;
+ private final DisplayController.DisplayInfoChangeListener mDispInfoChangeListener;
private boolean mUserUnlocked = false;
@@ -105,6 +113,7 @@
SystemUiProxy.INSTANCE.get(mContext), new Handler());
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
mNavBarKidsModeListener = isNavBarKidsMode -> recreateTaskbar();
+ // TODO(b/227669780): Consolidate this w/ DisplayController callbacks
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@@ -116,7 +125,7 @@
int configDiff = mOldConfig.diff(newConfig);
int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
| ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_SCREEN_SIZE;
+ | ActivityInfo.CONFIG_SCREEN_SIZE;
boolean requiresRecreate = (configDiff & configsRequiringRecreate) != 0;
if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
&& mTaskbarActivityContext != null && dp != null) {
@@ -151,8 +160,12 @@
public void onLowMemory() { }
};
mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
-
- mDisplayController.addChangeListener(this);
+ mDispInfoChangeListener = (context, info, flags) -> {
+ if ((flags & CHANGE_FLAGS) != 0) {
+ recreateTaskbar();
+ }
+ };
+ mDisplayController.addChangeListener(mDispInfoChangeListener);
SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
mUserSetupCompleteListener);
SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
@@ -163,13 +176,6 @@
recreateTaskbar();
}
- @Override
- public void onDisplayInfoChanged(Context context, Info info, int flags) {
- if ((flags & CHANGE_FLAGS) != 0) {
- recreateTaskbar();
- }
- }
-
private void destroyExistingTaskbar() {
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.onDestroy();
@@ -178,6 +184,17 @@
}
/**
+ * Displays a frame of the first Launcher reveal animation.
+ *
+ * This should be used to run a first Launcher reveal animation whose progress matches a swipe
+ * progress.
+ */
+ public AnimatorPlaybackController createLauncherStartFromSuwAnim(int duration) {
+ return mTaskbarActivityContext == null
+ ? null : mTaskbarActivityContext.createLauncherStartFromSuwAnim(duration);
+ }
+
+ /**
* Called when the user is unlocked
*/
public void onUserUnlocked() {
@@ -189,6 +206,9 @@
* Sets a {@link StatefulActivity} to act as taskbar callback
*/
public void setActivity(@NonNull StatefulActivity activity) {
+ if (mActivity == activity) {
+ return;
+ }
mActivity = activity;
mUnfoldProgressProvider.setSourceProvider(getUnfoldTransitionProgressProviderForActivity(
activity));
@@ -310,7 +330,7 @@
*/
public void destroy() {
destroyExistingTaskbar();
- mDisplayController.removeChangeListener(this);
+ mDisplayController.removeChangeListener(mDispInfoChangeListener);
SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
mUserSetupCompleteListener);
SettingsCache.INSTANCE.get(mContext).unregister(NAV_BAR_KIDS_MODE,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 62392ee..75881a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -36,6 +36,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.function.Predicate;
/**
* Launcher model Callbacks for rendering taskbar.
@@ -126,16 +127,16 @@
}
@Override
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
if (handleItemsRemoved(matcher)) {
commitItemsToUI();
}
}
- private boolean handleItemsRemoved(ItemInfoMatcher matcher) {
+ private boolean handleItemsRemoved(Predicate<ItemInfo> matcher) {
boolean modified = false;
for (int i = mHotseatItems.size() - 1; i >= 0; i--) {
- if (matcher.matchesInfo(mHotseatItems.valueAt(i))) {
+ if (matcher.test(mHotseatItems.valueAt(i))) {
modified = true;
mHotseatItems.removeAt(i);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 1ccad78..7b4501a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -43,6 +43,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
@@ -136,7 +137,7 @@
return null;
}
ItemInfo item = (ItemInfo) icon.getTag();
- if (!PopupContainerWithArrow.canShow(icon, item)) {
+ if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
@@ -159,7 +160,7 @@
mPopupDataProvider.getNotificationKeysForItem(item),
// TODO (b/198438631): add support for INSTALL shortcut factory
getSystemShortcuts()
- .map(s -> s.getShortcut(context, item))
+ .map(s -> s.getShortcut(context, item, icon))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
container.requestFocus();
@@ -242,7 +243,8 @@
*/
private SystemShortcut.Factory<BaseTaskbarContext> createSplitShortcutFactory(
SplitPositionOption position) {
- return (context, itemInfo) -> new TaskbarSplitShortcut(context, itemInfo, position);
+ return (context, itemInfo, originalView) -> new TaskbarSplitShortcut(context, itemInfo,
+ originalView, position);
}
/**
@@ -253,9 +255,9 @@
private static class TaskbarSplitShortcut extends SystemShortcut<BaseTaskbarContext> {
private final SplitPositionOption mPosition;
- TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo,
+ TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo, View originalView,
SplitPositionOption position) {
- super(position.iconResId, position.textResId, context, itemInfo);
+ super(position.iconResId, position.textResId, context, itemInfo, originalView);
mPosition = position;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
index 94a3307..1d3757f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
@@ -17,22 +17,19 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.views.ActivityContext;
+
/**
* View that handles scrimming the taskbar and the inverted corners it draws. The scrim is used
* when bubbles is expanded.
*/
public class TaskbarScrimView extends View {
- private final Paint mTaskbarScrimPaint;
- private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
+ private final TaskbarBackgroundRenderer mRenderer;
private boolean mShowScrim;
- private float mLeftCornerRadius, mRightCornerRadius;
- private float mBackgroundHeight;
public TaskbarScrimView(Context context) {
this(context, null);
@@ -49,14 +46,9 @@
public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- mTaskbarScrimPaint = new Paint();
- mTaskbarScrimPaint.setColor(getResources().getColor(android.R.color.system_neutral1_1000));
- mTaskbarScrimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
- mTaskbarScrimPaint.setStyle(Paint.Style.FILL);
-
- mInvertedLeftCornerPath = new Path();
- mInvertedRightCornerPath = new Path();
+ mRenderer = new TaskbarBackgroundRenderer(ActivityContext.lookupContext(context));
+ mRenderer.getPaint().setColor(getResources().getColor(
+ android.R.color.system_neutral1_1000));
}
@Override
@@ -64,31 +56,7 @@
super.onDraw(canvas);
if (mShowScrim) {
- canvas.save();
- canvas.translate(0, canvas.getHeight() - mBackgroundHeight);
-
- // Scrim the taskbar itself.
- canvas.drawRect(0, 0, canvas.getWidth(), mBackgroundHeight, mTaskbarScrimPaint);
-
- // Scrim the inverted rounded corners above the taskbar.
- canvas.translate(0, -mLeftCornerRadius);
- canvas.drawPath(mInvertedLeftCornerPath, mTaskbarScrimPaint);
- canvas.translate(0, mLeftCornerRadius);
- canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
- canvas.drawPath(mInvertedRightCornerPath, mTaskbarScrimPaint);
-
- canvas.restore();
- }
- }
-
- /**
- * Sets the height of the taskbar background.
- * @param height the height of the background.
- */
- protected void setBackgroundHeight(float height) {
- mBackgroundHeight = height;
- if (mShowScrim) {
- invalidate();
+ mRenderer.draw(canvas);
}
}
@@ -98,32 +66,7 @@
*/
protected void setScrimAlpha(float alpha) {
mShowScrim = alpha > 0f;
- mTaskbarScrimPaint.setAlpha((int) (alpha * 255));
+ mRenderer.getPaint().setAlpha((int) (alpha * 255));
invalidate();
}
-
- /**
- * Sets the radius of the left and right corners above the taskbar.
- * @param leftCornerRadius the radius of the left corner.
- * @param rightCornerRadius the radius of the right corner.
- */
- protected void setCornerSizes(float leftCornerRadius, float rightCornerRadius) {
- mLeftCornerRadius = leftCornerRadius;
- mRightCornerRadius = rightCornerRadius;
-
- Path square = new Path();
- square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
- Path circle = new Path();
- circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
- mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
- square.reset();
- square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
- circle.reset();
- circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
- mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-
- if (mShowScrim) {
- invalidate();
- }
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 02bbae4..58ace17 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -49,9 +49,6 @@
public TaskbarScrimViewController(TaskbarActivityContext activity, TaskbarScrimView scrimView) {
mActivity = activity;
mScrimView = scrimView;
- mScrimView.setCornerSizes(mActivity.getLeftCornerRadius(),
- mActivity.getRightCornerRadius());
- mScrimView.setBackgroundHeight(mActivity.getDeviceProfile().taskbarSize);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index a5c55b0..87b3789 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -25,5 +25,4 @@
public boolean setupUIVisible = false;
public boolean allAppsVisible = false;
-
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index be67136..fc9f9d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -17,6 +17,7 @@
import static android.view.HapticFeedbackConstants.LONG_PRESS;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
import static com.android.launcher3.taskbar.Utilities.appendFlag;
@@ -30,9 +31,11 @@
import android.content.SharedPreferences;
import android.util.Log;
import android.view.ViewConfiguration;
+import android.view.WindowInsets;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -166,7 +169,7 @@
mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarSize;
}
- public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
+ public void init(TaskbarControllers controllers, boolean setupUIVisible) {
mControllers = controllers;
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
@@ -187,7 +190,7 @@
boolean isManuallyStashedInApp = supportsManualStashing()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
- boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
+ boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
@@ -201,7 +204,7 @@
* state.
*/
public boolean supportsVisualStashing() {
- return !mActivity.isThreeButtonNav();
+ return mControllers.uiController.supportsVisualStashing();
}
/**
@@ -274,12 +277,14 @@
return !mIsStashed && isInApp();
}
- private boolean isInApp() {
+ public boolean isInApp() {
return hasAnyFlag(FLAGS_IN_APP);
}
/**
* Returns the height that taskbar will inset when inside apps.
+ * @see WindowInsets.Type#navigationBars()
+ * @see WindowInsets.Type#systemBars()
*/
public int getContentHeightToReportToApps() {
if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
@@ -304,6 +309,15 @@
return mUnstashedHeight;
}
+ /**
+ * Returns the height that taskbar will inset when inside apps.
+ * @see WindowInsets.Type#tappableElement()
+ */
+ public int getTappableHeightToReportToApps() {
+ int contentHeight = getContentHeightToReportToApps();
+ return contentHeight <= mStashedHeight ? 0 : contentHeight;
+ }
+
public int getStashedHeight() {
return mStashedHeight;
}
@@ -356,12 +370,33 @@
}
/**
+ * Adds the Taskbar unstash to Hotseat animator to the animator set.
+ *
+ * This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a
+ * swipe progress.
+ *
+ * @param placeholderDuration a placeholder duration to be used to ensure all full-length
+ * sub-animations are properly coordinated. This duration should not
+ * actually be used since this animation tracks a swipe progress.
+ */
+ protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+ createAnimToIsStashed(
+ /* isStashed= */ false,
+ placeholderDuration,
+ /* startDelay= */ 0,
+ /* animateBg= */ false);
+ animation.play(mAnimator);
+ }
+
+ /**
* Create a stash animation and save to {@link #mAnimator}.
* @param isStashed whether it's a stash animation or an unstash animation
* @param duration duration of the animation
* @param startDelay how many milliseconds to delay the animation after starting it.
+ * @param animateBg whether the taskbar's background should be animated
*/
- private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
+ private void createAnimToIsStashed(
+ boolean isStashed, long duration, long startDelay, boolean animateBg) {
if (mAnimator != null) {
mAnimator.cancel();
}
@@ -396,10 +431,14 @@
secondHalfDurationScale = 0.5f;
final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
- fullLengthAnimatorSet.playTogether(
- mTaskbarBackgroundOffset.animateToValue(1),
- mIconTranslationYForStash.animateToValue(stashTranslation)
- );
+ fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
+ if (animateBg) {
+ fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1));
+ } else {
+ fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback(
+ () -> mTaskbarBackgroundOffset.updateValue(1)));
+ }
+
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
@@ -412,10 +451,15 @@
secondHalfDurationScale = 0.75f;
fullLengthAnimatorSet.playTogether(
- mTaskbarBackgroundOffset.animateToValue(0),
mIconScaleForStash.animateToValue(1),
- mIconTranslationYForStash.animateToValue(0)
- );
+ mIconTranslationYForStash.animateToValue(0));
+ if (animateBg) {
+ fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0));
+ } else {
+ fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback(
+ () -> mTaskbarBackgroundOffset.updateValue(0)));
+ }
+
firstHalfAnimatorSet.playTogether(
mTaskbarStashedHandleAlpha.animateToValue(0)
);
@@ -442,7 +486,7 @@
@Override
public void onAnimationStart(Animator animation) {
mIsStashed = isStashed;
- onIsStashed(mIsStashed);
+ onIsStashedChanged(mIsStashed);
}
@Override
@@ -489,8 +533,11 @@
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
- private void onIsStashed(boolean isStashed) {
- mControllers.stashedHandleViewController.onIsStashed(isStashed);
+ private void onIsStashedChanged(boolean isStashed) {
+ mControllers.runAfterInit(() -> {
+ mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
+ mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+ });
}
public void applyState() {
@@ -549,7 +596,8 @@
updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, false);
if (applyState) {
- applyState(TaskbarAllAppsSlideInView.DEFAULT_CLOSE_DURATION);
+ applyState(ALL_APPS.getTransitionDuration(
+ mControllers.taskbarActivityContext, false /* isToState */));
}
}
@@ -712,7 +760,7 @@
mIsStashed = isStashed;
// This sets mAnimator.
- createAnimToIsStashed(mIsStashed, duration, startDelay);
+ createAnimToIsStashed(mIsStashed, duration, startDelay, /* animateBg= */ true);
if (start) {
mAnimator.start();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d5c399b..fcc34c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -22,6 +22,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import java.io.PrintWriter;
import java.util.stream.Stream;
/**
@@ -48,6 +49,11 @@
return true;
}
+ public boolean supportsVisualStashing() {
+ if (mControllers == null) return false;
+ return !mControllers.taskbarActivityContext.isThreeButtonNav();
+ }
+
protected void onStashedInAppChanged() { }
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
@@ -86,4 +92,12 @@
stashController.applyState();
}
}
+
+ @CallSuper
+ protected void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(String.format(
+ "%sTaskbarUIController: using an instance of %s",
+ prefix,
+ getClass().getSimpleName()));
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 8291475..6f88d64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -41,12 +41,13 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.uioverrides.ApiWrapper;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.AllAppsButton;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import java.util.function.Predicate;
+
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
@@ -312,8 +313,8 @@
if (!mTouchEnabled) {
return true;
}
- if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
- // Don't allow long pressing between icons.
+ if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
+ // Don't allow long pressing between icons, or above/below them.
return true;
}
if (mControllerCallbacks.onTouchEvent(event)) {
@@ -424,8 +425,8 @@
* Finds the first icon to match one of the given matchers, from highest to lowest priority.
* @return The first match, or All Apps button if no match was found.
*/
- public View getFirstMatch(ItemInfoMatcher... matchers) {
- for (ItemInfoMatcher matcher : matchers) {
+ public View getFirstMatch(Predicate<ItemInfo>... matchers) {
+ for (Predicate<ItemInfo> matcher : matchers) {
for (int i = 0; i < getChildCount(); i++) {
View item = getChildAt(i);
if (!(item.getTag() instanceof ItemInfo)) {
@@ -433,7 +434,7 @@
continue;
}
ItemInfo info = (ItemInfo) item.getTag();
- if (matcher.matchesInfo(info)) {
+ if (matcher.test(info)) {
return item;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e1ce898..3dd7932 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
import static com.android.quickstep.AnimatedFloat.VALUE;
import android.annotation.NonNull;
@@ -46,6 +47,7 @@
import com.android.quickstep.AnimatedFloat;
import java.io.PrintWriter;
+import java.util.function.Predicate;
/**
* Handles properties/data collection, then passes the results to TaskbarView to render.
@@ -72,6 +74,7 @@
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
this::updateTranslationY);
private AnimatedFloat mTaskbarNavButtonTranslationY;
+ private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
private final AnimatedFloat mThemeIconsBackground = new AnimatedFloat(
this::updateIconsBackground);
@@ -111,6 +114,8 @@
}
mTaskbarNavButtonTranslationY =
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
+ mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
+ .getTaskbarNavButtonTranslationYForInAppDisplay();
}
public void onDestroy() {
@@ -241,6 +246,7 @@
int offsetY = launcherDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, LINEAR);
+ setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, LINEAR);
if (Utilities.isDarkTheme(mTaskbarView.getContext())) {
setter.addFloat(mThemeIconsBackground, VALUE, 0f, 1f, LINEAR);
@@ -252,19 +258,21 @@
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
- int count = mTaskbarView.getChildCount();
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
- int positionInHotseat = -1;
- if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get() && i == count - 1) {
+ int positionInHotseat;
+ if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()
+ && child == mTaskbarView.getAllAppsButtonView()) {
// Note that there is no All Apps button in the hotseat, this position is only used
// as its convenient for animation purposes.
positionInHotseat = Utilities.isRtl(child.getResources())
? -1
- : mActivity.getDeviceProfile().inv.numShownHotseatIcons;
+ : mActivity.getDeviceProfile().numShownHotseatIcons;
- setter.setViewAlpha(child, 0, LINEAR);
+ if (!FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
+ setter.setViewAlpha(child, 0, LINEAR);
+ }
} else if (child.getTag() instanceof ItemInfo) {
positionInHotseat = ((ItemInfo) child.getTag()).screenId;
} else {
@@ -288,10 +296,12 @@
}
public void onRotationChanged(DeviceProfile deviceProfile) {
- if (areIconsVisible()) {
+ if (mControllers.taskbarStashController.isInApp()) {
// We only translate on rotation when on home
return;
}
+ mActivity.setTaskbarWindowHeight(
+ deviceProfile.taskbarSize + deviceProfile.getTaskbarOffsetY());
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
@@ -308,8 +318,8 @@
* 2) FolderIcon of the Folder containing the given icon
* 3) All Apps button
*/
- public View getFirstIconMatch(ItemInfoMatcher matcher) {
- ItemInfoMatcher folderMatcher = ItemInfoMatcher.forFolderMatch(matcher);
+ public View getFirstIconMatch(Predicate<ItemInfo> matcher) {
+ Predicate<ItemInfo> folderMatcher = ItemInfoMatcher.forFolderMatch(matcher);
return mTaskbarView.getFirstMatch(matcher, folderMatcher);
}
@@ -341,7 +351,10 @@
}
public View.OnClickListener getAllAppsButtonClickListener() {
- return v -> mControllers.taskbarAllAppsController.show();
+ return v -> {
+ mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
+ mControllers.taskbarAllAppsController.show();
+ };
}
public View.OnLongClickListener getIconOnLongClickListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 9fca8eb..eaf9384 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -38,7 +38,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
-import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -72,7 +71,6 @@
};
private TaskbarControllers mControllers;
- private TaskbarSharedState mSharedState;
/** Window context for all apps if it is open. */
private @Nullable TaskbarAllAppsContext mAllAppsContext;
@@ -88,18 +86,17 @@
}
/** Initialize the controller. */
- public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
+ public void init(TaskbarControllers controllers, boolean allAppsVisible) {
if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
return;
}
mControllers = controllers;
- mSharedState = sharedState;
/*
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
* changed).
*/
- if (mSharedState.allAppsVisible) {
+ if (allAppsVisible) {
show(false);
}
}
@@ -141,7 +138,9 @@
return;
}
mProxyView.show();
- mSharedState.allAppsVisible = true;
+ // mControllers and getSharedState should never be null here. Do not handle null-pointer
+ // to catch invalid states.
+ mControllers.getSharedState().allAppsVisible = true;
mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
this,
@@ -176,7 +175,9 @@
return;
}
mProxyView.close(false);
- mSharedState.allAppsVisible = false;
+ // mControllers and getSharedState should never be null here. Do not handle null-pointer
+ // to catch invalid states.
+ mControllers.getSharedState().allAppsVisible = false;
onDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index a37ebac..c4837a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -15,13 +15,16 @@
*/
package com.android.launcher3.taskbar.allapps;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@@ -33,9 +36,6 @@
/** Wrapper for taskbar all apps with slide-in behavior. */
public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarAllAppsContext>
implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
- static final int DEFAULT_OPEN_DURATION = 500;
- public static final int DEFAULT_CLOSE_DURATION = 200;
-
private TaskbarAllAppsContainerView mAppsView;
private OnCloseListener mOnCloseBeginListener;
private float mShiftRange;
@@ -61,7 +61,8 @@
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
- mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
+ mOpenCloseAnimator.setDuration(
+ ALL_APPS.getTransitionDuration(mActivityContext, true /* isToState */)).start();
} else {
mTranslationShift = TRANSLATION_SHIFT_OPENED;
}
@@ -80,7 +81,13 @@
@Override
protected void handleClose(boolean animate) {
Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
- handleClose(animate, DEFAULT_CLOSE_DURATION);
+ handleClose(animate,
+ ALL_APPS.getTransitionDuration(mActivityContext, false /* isToState */));
+ }
+
+ @Override
+ protected Interpolator getIdleInterpolator() {
+ return EMPHASIZED_ACCELERATE;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index f19b7de..a39e872 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3.taskbar.allapps;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_ALL_APPS;
-import static com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView.DEFAULT_OPEN_DURATION;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import com.android.launcher3.AbstractFloatingView;
@@ -81,7 +81,8 @@
private void setUpTaskbarStashing() {
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, true);
- mTaskbarStashController.applyState(DEFAULT_OPEN_DURATION);
+ mTaskbarStashController.applyState(
+ ALL_APPS.getTransitionDuration(mContext, true /* isToState */));
mSlideInView.setOnCloseBeginListener(() -> {
AbstractFloatingView.closeOpenContainer(
mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 351ec4a..5f3a990 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -180,7 +180,7 @@
: null;
super.applyFromWorkspaceItem(info, animate, staggerIndex);
int oldPlateColor = mPlateColor;
- int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
if (!animate) {
mPlateColor = newPlateColor;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f32b315..4bb4343 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -70,7 +70,6 @@
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.NavigationMode;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
@@ -86,6 +85,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Predicate;
import java.util.stream.Stream;
public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -220,8 +220,7 @@
(getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
- && !profile.isVerticalBarLayout()
- && profile.isPhone && !profile.isLandscape;
+ && !profile.isVerticalBarLayout();
UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
profile.hotseatBarSizePx);
}
@@ -246,7 +245,7 @@
}
@Override
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
super.bindWorkspaceComponentsRemoved(matcher);
mHotseatPredictionController.onModelItemsRemoved(matcher);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 947d3e2..00a98c0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -107,7 +107,6 @@
*/
private void handleSplitSelectionState(@NonNull LauncherState toState,
@Nullable PendingAnimation builder) {
- LauncherState currentState = mLauncher.getStateManager().getState();
boolean animate = builder != null;
PagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
@@ -116,39 +115,27 @@
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
mLauncher.getDeviceProfile());
- if (isSplitSelectionState(currentState, toState)) {
+ if (toState == OVERVIEW_SPLIT_SELECT) {
// Animation to "dismiss" selected taskView
PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation(
- toState.getTransitionDuration(mLauncher));
+ toState.getTransitionDuration(mLauncher, true /* isToState */));
// Add properties to shift remaining taskViews to get out of placeholder view
splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
toState.getSplitSelectTranslation(mLauncher), LINEAR);
splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
- if (!animate && isSplitSelectionState(currentState, toState)) {
+ if (!animate) {
splitSelectInitAnimation.buildAnim().start();
- } else if (animate &&
- isSplitSelectionState(currentState, toState)) {
+ } else {
builder.add(splitSelectInitAnimation.buildAnim());
}
- }
- if (isSplitSelectionState(currentState, toState)) {
mRecentsView.applySplitPrimaryScrollOffset();
} else {
mRecentsView.resetSplitPrimaryScrollOffset();
}
}
- /**
- * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
- * and {@param fromState} is not {@link LauncherState#OVERVIEW_SPLIT_SELECT}
- */
- private boolean isSplitSelectionState(@NonNull LauncherState fromState,
- @NonNull LauncherState toState) {
- return fromState != OVERVIEW_SPLIT_SELECT && toState == OVERVIEW_SPLIT_SELECT;
- }
-
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
LauncherState state) {
float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index df0ac7c..fe0bca6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -33,12 +33,12 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
public class PluginManagerWrapper {
@@ -48,6 +48,9 @@
public static final String PLUGIN_CHANGED = PluginManager.PLUGIN_CHANGED;
+ private static final UncaughtExceptionPreHandlerManager UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER =
+ new UncaughtExceptionPreHandlerManager();
+
private final Context mContext;
private final PluginManager mPluginManager;
private final PluginEnablerImpl mPluginEnabler;
@@ -67,7 +70,7 @@
mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
Utilities.IS_DEBUG_DEVICE,
- Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
+ UNCAUGHT_EXCEPTION_PRE_HANDLER_MANAGER, mPluginEnabler,
new PluginPrefs(c), privilegedPlugins);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index cd14b3f..3b1f119 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -20,6 +20,7 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -30,15 +31,19 @@
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS;
+ private static final int STATE_FLAGS =
+ FLAG_WORKSPACE_INACCESSIBLE | FLAG_CLOSE_POPUPS | FLAG_HOTSEAT_INACCESSIBLE;
public AllAppsState(int id) {
super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
}
@Override
- public int getTransitionDuration(Context context) {
- return 150;
+ public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
+ int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ return !context.getDeviceProfile().isTablet && isToState
+ ? 600
+ : isToState ? 500 : 300;
}
@Override
@@ -53,17 +58,21 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation scaleAndTranslation =
- new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+ return new ScaleAndTranslation(0.97f, NO_OFFSET, NO_OFFSET);
+ }
+
+ @Override
+ public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if (launcher.getDeviceProfile().isTablet) {
- scaleAndTranslation.scale = 0.97f;
+ return getWorkspaceScaleAndTranslation(launcher);
} else {
ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
.getWorkspaceScaleAndTranslation(launcher);
- scaleAndTranslation.translationX = overviewScaleAndTranslation.translationX;
- scaleAndTranslation.translationY = overviewScaleAndTranslation.translationY;
+ return new ScaleAndTranslation(
+ NO_SCALE,
+ overviewScaleAndTranslation.translationX,
+ overviewScaleAndTranslation.translationY);
}
- return scaleAndTranslation;
}
@Override
@@ -88,7 +97,9 @@
@Override
public int getVisibleElements(Launcher launcher) {
- return ALL_APPS_CONTENT | HOTSEAT_ICONS;
+ // Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state.
+ return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS
+ : ALL_APPS_CONTENT;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 6f084a1..0c49e5f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -40,7 +40,7 @@
}
@Override
- public int getTransitionDuration(Context launcher) {
+ public int getTransitionDuration(Context launcher, boolean isToState) {
return 300;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 236454e..5f62749 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -22,10 +22,12 @@
import android.graphics.Rect;
import android.os.SystemProperties;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
import com.android.quickstep.util.LayoutUtils;
@@ -56,7 +58,7 @@
}
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
// In gesture modes, overview comes in all the way from the side, so give it more time.
return DisplayController.getNavigationMode(context).hasGestures ? 380 : 250;
}
@@ -64,9 +66,9 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
+ float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
recentsView.getTaskSize(sTempRect);
- float scale = (float) sTempRect.width() / workspacePageWidth;
+ float scale = (float) sTempRect.height() / workspacePageHeight;
float parallaxFactor = 0.5f;
return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
}
@@ -93,7 +95,13 @@
@Override
public boolean isTaskbarStashed(Launcher launcher) {
- return true;
+ if (launcher instanceof BaseQuickstepLauncher) {
+ LauncherTaskbarUIController uiController =
+ ((BaseQuickstepLauncher) launcher).getTaskbarUIController();
+
+ return uiController != null && uiController.supportsVisualStashing();
+ }
+ return super.isTaskbarStashed(launcher);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index b1d83e7..f2162b0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -42,6 +42,7 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
@@ -50,6 +51,8 @@
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import android.animation.ValueAnimator;
@@ -179,12 +182,20 @@
}
config.duration = Math.max(config.duration, mHintToNormalDuration);
} else if (fromState == ALL_APPS && toState == NORMAL) {
- config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
- 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
- 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
- config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
+ boolean isTablet = mActivity.getDeviceProfile().isTablet;
+ config.setInterpolator(ANIM_ALL_APPS_FADE,
+ isTablet ? FINAL_FRAME : Interpolators.clampToProgress(LINEAR,
+ 1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+ 1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
+ config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR,
1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
+ } else if (fromState == NORMAL && toState == ALL_APPS) {
+ if (mActivity.getDeviceProfile().isTablet) {
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_DECELERATE);
+ }
+ // TODO(b/231682175): centralize this setup in AllAppsSwipeController
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 86c42ca..34a6821 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -21,7 +21,8 @@
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
@@ -40,16 +41,14 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
@@ -112,7 +111,8 @@
return true;
}
if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
- && AssistantUtilities.isExcludedAssistantRunning()) {
+ && TopTaskTracker.INSTANCE.get(mLauncher).getCachedTopTask(false)
+ .isExcludedAssistant()) {
return true;
}
return false;
@@ -147,16 +147,10 @@
AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
} else if (mStartState == ALL_APPS) {
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- builder.setFloat(allAppsController, ALL_APPS_PROGRESS,
- -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
-
- // Slightly fade out all apps content to further distinguish from scrolling.
- StateAnimationConfig config = new StateAnimationConfig();
- config.duration = accuracy;
- config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
- .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
-
- allAppsController.setAlphas(mEndState, config, builder);
+ builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
+ -mPullbackDistance, PULLBACK_INTERPOLATOR);
+ builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
+ 0.5f, PULLBACK_INTERPOLATOR);
}
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 7ec1243..4da1d41 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -180,7 +180,7 @@
// Normally we compute the duration based on the velocity and distance to the given
// state, but since the hint state tracks the entire screen without a clear endpoint, we
// need to manually set the duration to a reasonable value.
- animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
+ animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 2ca59eb..53dc9dd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -27,6 +27,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
import static com.android.launcher3.logging.StatsLogManager.getLauncherAtomEvent;
@@ -451,9 +452,11 @@
.withSrcState(LAUNCHER_STATE_HOME)
.withDstState(targetState.statsLogOrdinal)
.log(getLauncherAtomEvent(mStartState.statsLogOrdinal, targetState.statsLogOrdinal,
- targetState.ordinal > mStartState.ordinal
- ? LAUNCHER_UNKNOWN_SWIPEUP
- : LAUNCHER_UNKNOWN_SWIPEDOWN));
+ targetState == QUICK_SWITCH
+ ? LAUNCHER_QUICKSWITCH_RIGHT
+ : targetState.ordinal > mStartState.ordinal
+ ? LAUNCHER_UNKNOWN_SWIPEUP
+ : LAUNCHER_UNKNOWN_SWIPEDOWN));
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 27a55c3..dbee9c1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -21,10 +21,9 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
@@ -131,10 +130,10 @@
boolean isTablet = mLauncher.getDeviceProfile().isTablet;
builder.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
? INSTANT
- : Interpolators.clampToProgress(ACCEL,
+ : Interpolators.clampToProgress(LINEAR,
ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD,
ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
- builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(ACCEL,
+ builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR,
ALL_APPS_SCRIM_VISIBLE_THRESHOLD,
ALL_APPS_SCRIM_OPAQUE_THRESHOLD));
return builder;
@@ -145,10 +144,10 @@
boolean isTablet = mLauncher.getDeviceProfile().isTablet;
builder.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
? FINAL_FRAME
- : Interpolators.clampToProgress(DEACCEL,
+ : Interpolators.clampToProgress(LINEAR,
1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
- builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
+ builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(LINEAR,
1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
return builder;
@@ -262,7 +261,7 @@
@Override
protected void onReachedFinalState(LauncherState toState) {
- super.onReinitToState(toState);
+ super.onReachedFinalState(toState);
if (toState == ALL_APPS) {
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6e80402..1b1cb85 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -66,6 +67,7 @@
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
@@ -102,7 +104,6 @@
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
-import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
@@ -114,6 +115,7 @@
import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
@@ -149,6 +151,7 @@
// Null if the recents animation hasn't started yet or has been canceled or finished.
protected @Nullable RecentsAnimationController mRecentsAnimationController;
+ protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
protected Q mRecentsView;
@@ -163,7 +166,11 @@
if (mActivity != activity) {
return;
}
+ if (mTaskAnimationManager != null) {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true);
+ }
mRecentsView = null;
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
mActivity = null;
}
};
@@ -429,6 +436,9 @@
mRecentsView.switchToScreenshot(snapshots, () -> {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.cleanupScreenshot();
+ } else if (mDeferredCleanupRecentsAnimationController != null) {
+ mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
+ mDeferredCleanupRecentsAnimationController = null;
}
});
mRecentsView.onRecentsAnimationComplete();
@@ -564,24 +574,12 @@
}
protected void notifyGestureAnimationStartToRecents() {
- ActivityManager.RunningTaskInfo[] runningTasks;
+ Task[] runningTasks;
if (mIsSwipeForStagedSplit) {
- int[] splitTaskIds =
- LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
- runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
- for (int i = 0; i < splitTaskIds.length; i++) {
- int taskId = splitTaskIds[i];
- // Order matters here, we want first indexed RunningTaskInfo to be leftTop task
- for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
- if (taskId == rti.taskId) {
- runningTasks[i] = rti;
- break;
- }
-
- }
- }
+ int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
+ runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
} else {
- runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
+ runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
}
mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
}
@@ -782,7 +780,7 @@
// We will handle the sysui flags based on the centermost task view.
mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
|| (quickswitchThresholdPassed && centermostTaskFlags != 0));
- mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
+ mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed);
// Provide a hint to WM the direction that we will be settling in case the animation
// needs to be canceled
mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
@@ -801,7 +799,7 @@
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
- mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
+ mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
mRecentsAnimationController = controller;
mRecentsAnimationTargets = targets;
@@ -845,6 +843,9 @@
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
mActivityInitListener.unregister();
+ // Cache the recents animation controller so we can defer its cleanup to after having
+ // properly cleaned up the screenshot without accidentally using it.
+ mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
if (mRecentsAnimationTargets != null) {
@@ -934,7 +935,13 @@
mLogDirectionUpOrLeft = velocity.x < 0;
}
mDownPos = downPos;
- handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
+ Runnable handleNormalGestureEndCallback = () ->
+ handleNormalGestureEnd(endVelocity, isFling, velocity, /* isCancel= */ false);
+ if (mRecentsView != null) {
+ mRecentsView.runOnPageScrollsInitialized(handleNormalGestureEndCallback);
+ } else {
+ handleNormalGestureEndCallback.run();
+ }
}
private void endRunningWindowAnim(boolean cancel) {
@@ -1018,35 +1025,41 @@
return false;
}
- private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
- boolean isCancel) {
+ private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity,
+ boolean isFlingY, boolean isCancel) {
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents
return RECENTS;
}
final GestureEndTarget endTarget;
- final boolean goingToNewTask;
+ final boolean canGoToNewTask;
if (mRecentsView != null) {
if (!hasTargets()) {
// If there are no running tasks, then we can assume that this is a continuation of
// the last gesture, but after the recents animation has finished
- goingToNewTask = true;
+ canGoToNewTask = true;
} else {
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
final int taskToLaunch = mRecentsView.getNextPage();
- goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
+ canGoToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
}
} else {
- goingToNewTask = false;
+ canGoToNewTask = false;
}
final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
- if (!isFling) {
+ final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_threshold_speed);
+ if (!isFlingY) {
if (isCancel) {
endTarget = LAST_TASK;
} else if (mDeviceState.isFullyGesturalNavMode()) {
- if (mIsMotionPaused) {
+ if (canGoToNewTask && isFlingX) {
+ // Flinging towards new task takes precedence over mIsMotionPaused (which only
+ // checks y-velocity).
+ endTarget = NEW_TASK;
+ } else if (mIsMotionPaused) {
endTarget = RECENTS;
- } else if (goingToNewTask) {
+ } else if (canGoToNewTask) {
endTarget = NEW_TASK;
} else {
endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
@@ -1054,26 +1067,22 @@
} else {
endTarget = reachedOverviewThreshold && mGestureStarted
? RECENTS
- : goingToNewTask
+ : canGoToNewTask
? NEW_TASK
: LAST_TASK;
}
} else {
// If swiping at a diagonal, base end target on the faster velocity.
boolean isSwipeUp = endVelocity < 0;
- boolean willGoToNewTaskOnSwipeUp =
- goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
+ boolean willGoToNewTask =
+ canGoToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
- if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
- endTarget = HOME;
- } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
- // If swiping at a diagonal, base end target on the faster velocity.
- endTarget = NEW_TASK;
+ if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
+ endTarget = willGoToNewTask ? NEW_TASK : HOME;
} else if (isSwipeUp) {
- endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp
- ? NEW_TASK : RECENTS;
+ endTarget = (!reachedOverviewThreshold && willGoToNewTask) ? NEW_TASK : RECENTS;
} else {
- endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
+ endTarget = willGoToNewTask ? NEW_TASK : LAST_TASK; // Swipe is downward.
}
}
@@ -1136,6 +1145,13 @@
} else if (endTarget == RECENTS) {
if (mRecentsView != null) {
int nearestPage = mRecentsView.getDestinationPage();
+ if (nearestPage == INVALID_PAGE) {
+ // Allow the snap to invalid page to catch future error cases.
+ Log.e(TAG,
+ "RecentsView destination page is invalid",
+ new IllegalStateException());
+ }
+
boolean isScrolling = false;
if (mRecentsView.getNextPage() != nearestPage) {
// We shouldn't really scroll to the next page when swiping up to recents.
@@ -1151,6 +1167,8 @@
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
}
+ } else if (endTarget == LAST_TASK && mRecentsView != null) {
+ mRecentsView.snapToPage(mRecentsView.getCurrentPage(), Math.toIntExact(duration));
}
// Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
@@ -1166,6 +1184,11 @@
}
private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+ if (mDp == null || !mDp.isGestureMode || mDownPos == null) {
+ // We probably never received an animation controller, skip logging.
+ return;
+ }
+
StatsLogManager.EventEnum event;
switch (endTarget) {
case HOME:
@@ -1189,11 +1212,6 @@
logger.withItemInfo(targetTask.getItemInfo());
}
- DeviceProfile dp = mDp;
- if (dp == null || mDownPos == null) {
- // We probably never received an animation controller, skip logging.
- return;
- }
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
? LOG_NO_OP_PAGE_INDEX
: mRecentsView.getNextPage();
@@ -1380,7 +1398,7 @@
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
- final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
+ final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
.getOrientationState();
final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
@@ -1579,9 +1597,6 @@
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
- if (mActivity != null) {
- mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
- }
}
/**
@@ -1782,8 +1797,7 @@
new PictureInPictureSurfaceTransaction.Builder()
.setAlpha(0f)
.build();
- int[] taskIds =
- LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+ int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
for (int taskId : taskIds) {
mRecentsAnimationController.setFinishTaskTransaction(taskId,
tx, null /* overlay */);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 9686510..2fcd286 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.util.DisplayController.getNavigationMode;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
@@ -62,7 +61,6 @@
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.SplitScreenBounds;
-import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -86,6 +84,8 @@
private STATE_TYPE mTargetState;
+ @Nullable private Runnable mOnInitBackgroundStateUICallback = null;
+
protected BaseActivityInterface(boolean rotationSupportedByActivity,
STATE_TYPE overviewState, STATE_TYPE backgroundState) {
this.rotationSupportedByActivity = rotationSupportedByActivity;
@@ -222,32 +222,27 @@
*/
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
Resources res = context.getResources();
+ float maxScale = res.getFloat(R.dimen.overview_max_scale);
if (dp.isTablet) {
Rect gridRect = new Rect();
- calculateGridSize(context, dp, gridRect);
+ calculateGridSize(dp, gridRect);
- PointF taskDimension = getTaskDimension(context, dp);
- float scale = gridRect.height() / taskDimension.y;
- scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
- int outWidth = Math.round(scale * taskDimension.x);
- int outHeight = Math.round(scale * taskDimension.y);
-
- int gravity = Gravity.CENTER;
- Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
+ calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
} else {
int taskMargin = dp.overviewTaskMarginPx;
calculateTaskSizeInternal(context, dp,
dp.overviewTaskThumbnailTopMarginPx,
- getOverviewActionsHeight(context, dp),
+ dp.getOverviewActionsClaimedSpace(),
res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+ maxScale,
+ Gravity.CENTER,
outRect);
}
}
- private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
- int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
+ int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
Rect outRect) {
- PointF taskDimension = getTaskDimension(context, dp);
Rect insets = dp.getInsets();
Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
@@ -258,13 +253,21 @@
minimumHorizontalPadding,
claimedSpaceBelow);
+ calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
+ }
+
+ private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+ Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
+ PointF taskDimension = getTaskDimension(context, dp);
+
float scale = Math.min(
potentialTaskRect.width() / taskDimension.x,
potentialTaskRect.height() / taskDimension.y);
+ scale = Math.min(scale, maxScale);
int outWidth = Math.round(scale * taskDimension.x);
int outHeight = Math.round(scale * taskDimension.y);
- Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+ Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
}
private static PointF getTaskDimension(Context context, DeviceProfile dp) {
@@ -314,10 +317,10 @@
/**
* Calculates the overview grid size for the provided device configuration.
*/
- public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+ public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
Rect insets = dp.getInsets();
int topMargin = dp.overviewTaskThumbnailTopMarginPx;
- int bottomMargin = getOverviewActionsHeight(context, dp);
+ int bottomMargin = dp.getOverviewActionsClaimedSpace();
int sideMargin = dp.overviewGridSideMargin;
outRect.set(0, 0, dp.widthPx, dp.heightPx);
@@ -352,21 +355,18 @@
* Calculates the modal taskView size for the provided device configuration
*/
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ calculateTaskSize(context, dp, outRect);
+ float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
calculateTaskSizeInternal(
context, dp,
dp.overviewTaskMarginPx,
- getOverviewActionsHeight(context, dp),
- dp.overviewTaskMarginPx,
+ dp.heightPx - outRect.bottom - dp.getInsets().bottom,
+ Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+ 1f /*maxScale*/,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
outRect);
}
- /** Gets the space that the overview actions will take, including bottom margin. */
- private int getOverviewActionsHeight(Context context, DeviceProfile dp) {
- return OverviewActionsView.getOverviewActionsBottomMarginPx(getNavigationMode(context), dp)
- + OverviewActionsView.getOverviewActionsTopMarginPx(getNavigationMode(context), dp)
- + dp.overviewActionsHeight;
- }
-
/**
* Called when the gesture ends and the animation starts towards the given target. Used to add
* an optional additional animation with the same duration.
@@ -414,6 +414,21 @@
return null;
}
+ protected void runOnInitBackgroundStateUI(Runnable callback) {
+ mOnInitBackgroundStateUICallback = callback;
+ ACTIVITY_TYPE activity = getCreatedActivity();
+ if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
+ onInitBackgroundStateUI();
+ }
+ }
+
+ private void onInitBackgroundStateUI() {
+ if (mOnInitBackgroundStateUICallback != null) {
+ mOnInitBackgroundStateUICallback.run();
+ mOnInitBackgroundStateUICallback = null;
+ }
+ }
+
public interface AnimationFactory {
void createActivityInterface(long transitionLength);
@@ -453,13 +468,14 @@
mStartState = mActivity.getStateManager().getState();
}
- protected ACTIVITY_TYPE initUI() {
+ protected ACTIVITY_TYPE initBackgroundStateUI() {
STATE_TYPE resetState = mStartState;
if (mStartState.shouldDisableRestore()) {
resetState = mActivity.getStateManager().getRestState();
}
mActivity.getStateManager().setRestState(resetState);
mActivity.getStateManager().goToState(mBackgroundState, false);
+ onInitBackgroundStateUI();
return mActivity;
}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 7feec2c..ba61574 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -89,7 +89,7 @@
boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
- factory.initUI();
+ factory.initBackgroundStateUI();
return factory;
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9828467..ee5bb44 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -30,7 +30,7 @@
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -69,7 +69,6 @@
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@@ -109,7 +108,7 @@
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
continuingLastGesture, inputConsumer);
- mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
+ mRunningOverHome = mGestureState.getRunningTask().isHomeTask();
if (mRunningOverHome) {
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
@@ -150,16 +149,17 @@
return new FallbackPipToHomeAnimationFactory();
}
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
- startHomeIntent(mActiveAnimationFactory);
+ startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
return mActiveAnimationFactory;
}
private void startHomeIntent(
- @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory) {
+ @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
+ @Nullable RemoteAnimationTargetCompat runningTaskTarget) {
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
Intent intent = new Intent(mGestureState.getHomeIntent());
- if (gestureContractAnimationFactory != null) {
- gestureContractAnimationFactory.addGestureContract(intent);
+ if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
+ gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
}
try {
mContext.startActivity(intent, options.toBundle());
@@ -187,7 +187,8 @@
// the PiP task appearing.
recentsCallback = () -> {
callback.run();
- startHomeIntent(null /* gestureContractAnimationFactory */);
+ startHomeIntent(
+ null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
};
} else {
recentsCallback = callback;
@@ -212,7 +213,7 @@
if (mRunningOverHome) {
if (DisplayController.getNavigationMode(mContext).hasGestures) {
mRecentsView.onGestureAnimationStartOnHome(
- new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()},
+ mGestureState.getRunningTask().getPlaceholderTasks(),
mDeviceState.getRotationTouchHelper());
}
} else {
@@ -396,12 +397,12 @@
}
}
- private void addGestureContract(Intent intent) {
- if (mRunningOverHome || mGestureState.getRunningTask() == null) {
+ private void addGestureContract(Intent intent, RunningTaskInfo runningTaskInfo) {
+ if (mRunningOverHome || runningTaskInfo == null) {
return;
}
- TaskKey key = new TaskKey(mGestureState.getRunningTask());
+ TaskKey key = new TaskKey(runningTaskInfo);
if (key.getComponent() != null) {
if (sMessageReceiver == null) {
sMessageReceiver = new StaticMessageReceiver();
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index ed0623d..3b52e91 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -22,7 +22,6 @@
import android.annotation.Nullable;
import android.annotation.TargetApi;
-import android.app.ActivityManager;
import android.content.Intent;
import android.os.Build;
@@ -30,6 +29,7 @@
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.tracing.GestureStateProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -135,8 +135,7 @@
private final MultiStateCallback mStateCallback;
private final int mGestureId;
- private ActivityManager.RunningTaskInfo mRunningTask;
- private ActivityManager.RunningTaskInfo[] mRunningTasks;
+ private CachedTaskInfo mRunningTask;
private GestureEndTarget mEndTarget;
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
@@ -232,42 +231,25 @@
/**
* @return the running task for this gesture.
*/
- public ActivityManager.RunningTaskInfo getRunningTask() {
+ public CachedTaskInfo getRunningTask() {
return mRunningTask;
}
/**
- * This will array will contain the task returned by {@link #getRunningTask()}
- * @return the running tasks for this gesture.
- */
- public ActivityManager.RunningTaskInfo[] getRunningTasks() {
- return mRunningTasks;
- }
-
- /**
* @return the running task id for this gesture.
*/
public int getRunningTaskId() {
- return mRunningTask != null ? mRunningTask.taskId : -1;
+ return mRunningTask != null ? mRunningTask.getTaskId() : -1;
}
/**
* Updates the running task for the gesture to be the given {@param runningTask}.
*/
- public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) {
+ public void updateRunningTask(CachedTaskInfo runningTask) {
mRunningTask = runningTask;
}
/**
- * TODO(b/210903248) refactor to consolidate w/ method above
- * Updates the running task for the gesture to be the given {@param runningTask}.
- */
- public void updateRunningTasks(ActivityManager.RunningTaskInfo[] runningTasks) {
- mRunningTasks = runningTasks;
- updateRunningTask(runningTasks[0]);
- }
-
- /**
* Updates the last task that appeared during this gesture.
*/
public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
@@ -339,7 +321,7 @@
* user controlled gesture.
*/
public void setHandlingAtomicEvent(boolean handlingAtomicEvent) {
- mHandlingAtomicEvent = true;
+ mHandlingAtomicEvent = handlingAtomicEvent;
}
/**
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
index a768ef5..758c6e0 100644
--- a/quickstep/src/com/android/quickstep/KtR.java
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -31,6 +31,7 @@
public static final class dimen {
public static int task_menu_spacing = R.dimen.task_menu_spacing;
public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
+ public static int taskbar_ime_size = R.dimen.taskbar_ime_size;
}
public static final class layout {
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 10a3a2e..c13b95f 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -135,7 +135,7 @@
}
};
- BaseQuickstepLauncher launcher = factory.initUI();
+ BaseQuickstepLauncher launcher = factory.initBackgroundStateUI();
// Since all apps is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
launcher.getAppsView().reset(false /* animate */);
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7abcbdb..fd9f922 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
@@ -25,19 +26,20 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
-import android.util.Log;
-import android.util.MathUtils;
import android.util.Pair;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.IOnBackInvokedCallback;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -45,6 +47,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+
/**
* Controls the animation of swiping back and returning to launcher.
*
@@ -53,7 +56,7 @@
* the app window and plays the rest of app close transitions in one go.
*
* This animation is used only for apps that enable back dispatching via
- * {@link android.view.OnBackInvokedDispatcher}. The controller registers
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
* an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
* navigation to launcher starts.
*
@@ -62,34 +65,36 @@
*
*/
public class LauncherBackAnimationController {
- private static final int CANCEL_TRANSITION_DURATION = 150;
- private static final String TAG = "LauncherBackAnimationController";
- private final DeviceProfile mDeviceProfile;
+ private static final int CANCEL_TRANSITION_DURATION = 233;
+ private static final float MIN_WINDOW_SCALE = 0.7f;
private final QuickstepTransitionManager mQuickstepTransitionManager;
private final Matrix mTransformMatrix = new Matrix();
- private final RectF mTargetRectF = new RectF();
- private final RectF mStartRectF = new RectF();
+ /** The window position at the beginning of the back animation. */
+ private final Rect mStartRect = new Rect();
+ /** The window position when the back gesture is cancelled. */
+ private final RectF mCancelRect = new RectF();
+ /** The current window position. */
private final RectF mCurrentRect = new RectF();
private final BaseQuickstepLauncher mLauncher;
private final int mWindowScaleMarginX;
- private final int mWindowScaleMarginY;
+ /** Max window translation in the Y axis. */
+ private final int mWindowMaxDeltaY;
private final float mWindowScaleEndCornerRadius;
private final float mWindowScaleStartCornerRadius;
+ private final Interpolator mCancelInterpolator;
+ private final PointF mInitialTouchPos = new PointF();
private RemoteAnimationTargetCompat mBackTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mSpringAnimationInProgress = false;
private boolean mAnimatorSetInProgress = false;
- @BackEvent.SwipeEdge
- private int mSwipeEdge;
private float mBackProgress = 0;
private boolean mBackInProgress = false;
+ private IOnBackInvokedCallback mBackCallback;
public LauncherBackAnimationController(
- DeviceProfile deviceProfile,
BaseQuickstepLauncher launcher,
QuickstepTransitionManager quickstepTransitionManager) {
- mDeviceProfile = deviceProfile;
mLauncher = launcher;
mQuickstepTransitionManager = quickstepTransitionManager;
mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
@@ -100,8 +105,10 @@
mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
R.dimen.swipe_back_window_scale_x_margin);
- mWindowScaleMarginY = mLauncher.getResources().getDimensionPixelSize(
- R.dimen.swipe_back_window_scale_y_margin);
+ mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_max_delta_y);
+ mCancelInterpolator =
+ AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
}
/**
@@ -109,47 +116,45 @@
* @param handler Handler to the thread to run the animations on.
*/
public void registerBackCallbacks(Handler handler) {
- SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
- if (systemUiProxy == null) {
- Log.e(TAG, "SystemUiProxy is null. Skip registering back invocation callbacks");
- return;
- }
- systemUiProxy.setBackToLauncherCallback(
- new IOnBackInvokedCallback.Stub() {
- @Override
- public void onBackCancelled() {
- handler.post(() -> resetPositionAnimated());
- }
+ mBackCallback = new IOnBackInvokedCallback.Stub() {
+ @Override
+ public void onBackCancelled() {
+ handler.post(() -> resetPositionAnimated());
+ }
- @Override
- public void onBackInvoked() {
- handler.post(() -> startTransition());
- }
+ @Override
+ public void onBackInvoked() {
+ handler.post(() -> startTransition());
+ }
- @Override
- public void onBackProgressed(BackEvent backEvent) {
- mBackProgress = backEvent.getProgress();
- // TODO: Update once the interpolation curve spec is finalized.
- mBackProgress =
- 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
- - mBackProgress);
- if (!mBackInProgress) {
- startBack(backEvent);
- } else {
- updateBackProgress(mBackProgress);
- }
- }
+ @Override
+ public void onBackProgressed(BackEvent backEvent) {
+ mBackProgress = backEvent.getProgress();
+ // TODO: Update once the interpolation curve spec is finalized.
+ mBackProgress =
+ 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
+ - mBackProgress);
+ if (!mBackInProgress) {
+ startBack(backEvent);
+ } else {
+ updateBackProgress(mBackProgress, backEvent);
+ }
+ }
- public void onBackStarted() { }
- });
+ @Override
+ public void onBackStarted() { }
+ };
+ SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback);
}
private void resetPositionAnimated() {
- ValueAnimator cancelAnimator = ValueAnimator.ofFloat(mBackProgress, 0);
+ ValueAnimator cancelAnimator = ValueAnimator.ofFloat(0, 1);
+ mCancelRect.set(mCurrentRect);
cancelAnimator.setDuration(CANCEL_TRANSITION_DURATION);
+ cancelAnimator.setInterpolator(mCancelInterpolator);
cancelAnimator.addUpdateListener(
animation -> {
- updateBackProgress((float) animation.getAnimatedValue());
+ updateCancelProgress((float) animation.getAnimatedValue());
});
cancelAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -162,10 +167,10 @@
/** Unregisters the back to launcher callback in shell. */
public void unregisterBackCallbacks() {
- SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
- if (systemUiProxy != null) {
- systemUiProxy.clearBackToLauncherCallback();
+ if (mBackCallback != null) {
+ SystemUiProxy.INSTANCE.get(mLauncher).clearBackToLauncherCallback(mBackCallback);
}
+ mBackCallback = null;
}
private void startBack(BackEvent backEvent) {
@@ -179,50 +184,72 @@
mTransaction.show(appTarget.leash).apply();
mTransaction.setAnimationTransaction();
mBackTarget = new RemoteAnimationTargetCompat(appTarget);
- mSwipeEdge = backEvent.getSwipeEdge();
- float screenWidth = mDeviceProfile.widthPx;
- float screenHeight = mDeviceProfile.heightPx;
- float targetHeight = screenHeight - 2 * mWindowScaleMarginY;
- float targetWidth = targetHeight * screenWidth / screenHeight;
- float left;
- if (mSwipeEdge == BackEvent.EDGE_LEFT) {
- left = screenWidth - targetWidth - mWindowScaleMarginX;
- } else {
- left = mWindowScaleMarginX;
- }
- float top = mWindowScaleMarginY;
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
// TODO(b/218916755): Offset start rectangle in multiwindow mode.
- mStartRectF.set(0, 0, screenWidth, screenHeight);
- mTargetRectF.set(left, top, targetWidth + left, targetHeight + top);
+ mStartRect.set(mBackTarget.windowConfiguration.getMaxBounds());
}
- private void updateBackProgress(float progress) {
+ private void updateBackProgress(float progress, BackEvent event) {
if (mBackTarget == null) {
return;
}
+ float screenWidth = mStartRect.width();
+ float screenHeight = mStartRect.height();
+ float dX = Math.abs(event.getTouchX() - mInitialTouchPos.x);
+ // The 'follow width' is the width of the window if it completely matches
+ // the gesture displacement.
+ float followWidth = screenWidth - dX;
+ // The 'progress width' is the width of the window if it strictly linearly interpolates
+ // to minimum scale base on progress.
+ float progressWidth = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
+ // The final width is derived from interpolating between the follow with and progress width
+ // using gesture progress.
+ float width = Utilities.mapRange(progress, followWidth, progressWidth);
+ float height = screenHeight / screenWidth * width;
+ float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY;
+ // Move the window along the Y axis.
+ float top = (screenHeight - height) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float left = event.getSwipeEdge() == BackEvent.EDGE_RIGHT
+ ? progress * mWindowScaleMarginX
+ : screenWidth - progress * mWindowScaleMarginX - width;
- mCurrentRect.set(
- MathUtils.lerp(mStartRectF.left, mTargetRectF.left, progress),
- MathUtils.lerp(mStartRectF.top, mTargetRectF.top, progress),
- MathUtils.lerp(mStartRectF.right, mTargetRectF.right, progress),
- MathUtils.lerp(mStartRectF.bottom, mTargetRectF.bottom, progress));
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder =
- new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash);
-
- Rect currentRect = new Rect();
- mCurrentRect.round(currentRect);
-
- // Scale the target window to match the currentRectF.
- final float scale = mCurrentRect.width() / mStartRectF.width();
- mTransformMatrix.reset();
- mTransformMatrix.setScale(scale, scale);
- mTransformMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
- Rect startRect = new Rect();
- mStartRectF.round(startRect);
+ mCurrentRect.set(left, top, left + width, top + height);
float cornerRadius = Utilities.mapRange(
progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
+ applyTransform(mCurrentRect, cornerRadius);
+ }
+
+ private void updateCancelProgress(float progress) {
+ if (mBackTarget == null) {
+ return;
+ }
+ mCurrentRect.set(
+ Utilities.mapRange(progress, mCancelRect.left, mStartRect.left),
+ Utilities.mapRange(progress, mCancelRect.top, mStartRect.top),
+ Utilities.mapRange(progress, mCancelRect.right, mStartRect.right),
+ Utilities.mapRange(progress, mCancelRect.bottom, mStartRect.bottom));
+
+ float endCornerRadius = Utilities.mapRange(
+ mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
+ float cornerRadius = Utilities.mapRange(
+ progress, endCornerRadius, mWindowScaleStartCornerRadius);
+ applyTransform(mCurrentRect, cornerRadius);
+ }
+
+ /** Transform the target window to match the target rect. */
+ private void applyTransform(RectF targetRect, float cornerRadius) {
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder =
+ new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash);
+ final float scale = targetRect.width() / mStartRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
builder.withMatrix(mTransformMatrix)
- .withWindowCrop(startRect)
+ .withWindowCrop(mStartRect)
.withCornerRadius(cornerRadius);
SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = builder.build();
@@ -249,12 +276,19 @@
mLauncher.getStateManager().moveToRestState();
}
+ // Explicitly close opened floating views (which is typically called from
+ // Launcher#onResumed, but in the predictive back flow launcher is not resumed until
+ // the transition is fully finished.)
+ AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
+ float cornerRadius = Utilities.mapRange(
+ mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
Pair<RectFSpringAnim, AnimatorSet> pair =
mQuickstepTransitionManager.createWallpaperOpenAnimations(
new RemoteAnimationTargetCompat[]{mBackTarget},
new RemoteAnimationTargetCompat[]{},
false /* fromUnlock */,
- mCurrentRect);
+ mCurrentRect,
+ cornerRadius);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
}
@@ -263,17 +297,14 @@
mBackTarget = null;
mBackInProgress = false;
mBackProgress = 0;
- mSwipeEdge = BackEvent.EDGE_LEFT;
mTransformMatrix.reset();
- mTargetRectF.setEmpty();
+ mCancelRect.setEmpty();
mCurrentRect.setEmpty();
- mStartRectF.setEmpty();
+ mStartRect.setEmpty();
+ mInitialTouchPos.set(0, 0);
mAnimatorSetInProgress = false;
mSpringAnimationInProgress = false;
- SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
- if (systemUiProxy != null) {
- SystemUiProxy.INSTANCE.getNoCreate().onBackToLauncherAnimationFinished();
- }
+ SystemUiProxy.INSTANCE.get(mLauncher).onBackToLauncherAnimationFinished();
}
private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 42f9eb6..dffdc5a 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -217,7 +217,8 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
- interactionHandler.onGestureEnded(0, new PointF(), new PointF());
+ activityInterface.runOnInitBackgroundStateUI(() ->
+ interactionHandler.onGestureEnded(0, new PointF(), new PointF()));
cmd.removeListener(this);
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index ef81449..528fb97 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -66,6 +66,12 @@
mDeviceProfile.overviewPageSpacing);
return response;
}
+
+ case TestProtocol.REQUEST_HAS_TIS: {
+ response.putBoolean(
+ TestProtocol.REQUEST_HAS_TIS, true);
+ return response;
+ }
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index fe31f1d..51ae56b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -104,8 +104,7 @@
.map(RemoteAnimationTargetCompat::unwrap)
.toArray(RemoteAnimationTarget[]::new);
- RemoteAnimationTarget[] nonAppTargets =
- mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps);
+ RemoteAnimationTarget[] nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(nonHomeApps);
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index c120b32..2007ee1 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRecentsAnimationController;
@@ -97,24 +98,20 @@
* Indicates that the gesture has crossed the window boundary threshold and we should minimize
* if we are in splitscreen.
*/
- public void setSplitScreenMinimized(boolean splitScreenMinimized) {
+ public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
if (!mAllowMinimizeSplitScreen) {
return;
}
if (mSplitScreenMinimized != splitScreenMinimized) {
mSplitScreenMinimized = splitScreenMinimized;
- UI_HELPER_EXECUTOR.execute(() -> {
- SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
- if (p != null) {
- p.setSplitScreenMinimized(splitScreenMinimized);
- }
- });
+ UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(context)
+ .setSplitScreenMinimized(splitScreenMinimized));
}
}
/**
* Remove task remote animation target from
- * {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
+ * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
*/
@UiThread
public void removeTaskTarget(@NonNull RemoteAnimationTargetCompat target) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 7ad60bb..4fb7e6b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,14 +45,11 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
@@ -61,18 +58,17 @@
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
-import android.text.TextUtils;
import android.view.MotionEvent;
import androidx.annotation.BinderThread;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.DisplayController.NavigationMode;
import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -83,7 +79,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.List;
/**
* Manages the state of the system during a swipe up gesture.
@@ -97,7 +92,6 @@
private final int mDisplayId;
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
- private final List<ComponentName> mGestureBlockedActivities;
// Cache for better performance since it doesn't change at runtime.
private final boolean mCanImeRenderGesturalNavButtons =
InputMethodService.canImeRenderGesturalNavButtons();
@@ -129,6 +123,7 @@
}
};
+ private int mGestureBlockingTaskId = -1;
private Region mExclusionRegion;
private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -178,22 +173,6 @@
onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
- // Add any blocked activities
- String[] blockingActivities;
- try {
- blockingActivities =
- context.getResources().getStringArray(R.array.gesture_blocking_activities);
- } catch (Resources.NotFoundException e) {
- blockingActivities = new String[0];
- }
- mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
- for (String blockingActivity : blockingActivities) {
- if (!TextUtils.isEmpty(blockingActivity)) {
- mGestureBlockedActivities.add(
- ComponentName.unflattenFromString(blockingActivity));
- }
- }
-
SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
@@ -367,11 +346,17 @@
}
/**
- * @return whether the given running task info matches the gesture-blocked activity.
+ * Sets the task id where gestures should be blocked
*/
- public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
- return runningTaskInfo != null
- && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
+ public void setGestureBlockingTaskId(int taskId) {
+ mGestureBlockingTaskId = taskId;
+ }
+
+ /**
+ * @return whether the given running task info matches the gesture-blocked task.
+ */
+ public boolean isGestureBlockedTask(CachedTaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.getTaskId() == mGestureBlockingTaskId;
}
/**
@@ -434,6 +419,13 @@
}
/**
+ * @return whether notification panel is expanded
+ */
+ public boolean isNotificationPanelExpanded() {
+ return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0;
+ }
+
+ /**
* @return whether the global actions dialog is showing
*/
public boolean isSystemUiDialogShowing() {
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 5d77a6e..d11d50b 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -54,7 +54,7 @@
* Singleton class to load and manage recents model.
*/
@TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel extends TaskStackChangeListener implements IconChangeListener {
+public class RecentsModel implements IconChangeListener, TaskStackChangeListener {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index afb4d4d..c3ea256 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -22,7 +22,6 @@
import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -50,8 +49,7 @@
* running tasks
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
- int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
- .getRunningSplitTaskIds();
+ int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
}
@@ -73,7 +71,7 @@
* Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
*
* If split screen may be active when this is called, you might want to use
- * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
+ * {@link #assignTargetsForSplitScreen(Context, RemoteAnimationTargets)}
*/
public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -90,9 +88,9 @@
* apps in targets.apps to that of the _active_ split screened tasks.
* See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
*/
- public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
- int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
- .getRunningSplitTaskIds();
+ public RemoteTargetHandle[] assignTargetsForSplitScreen(
+ Context context, RemoteAnimationTargets targets) {
+ int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
return assignTargetsForSplitScreen(targets, splitIds);
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f952e0d..f1e20db 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -74,7 +74,6 @@
@Override
public void onActivityRotation(int displayId) {
- super.onActivityRotation(displayId);
// This always gets called before onDisplayInfoChanged() so we know how to process
// the rotation in that method. This is done to avoid having a race condition between
// the sensor readings and onDisplayInfoChanged() call
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 8862073..088e1cf 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -37,7 +37,6 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
@@ -82,8 +81,7 @@
mGestureState = gestureState;
mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
- LauncherSplitScreenListener.INSTANCE.getNoCreate()
- .getRunningSplitTaskIds().length > 1;
+ TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 5ef89d3..39d8b54 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -676,14 +676,12 @@
* Call this when going to recents so that shell can set-up and provide appropriate leashes
* for animation (eg. DividerBar).
*
- * @param cancel true if recents starting is being cancelled.
* @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
*/
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- RemoteAnimationTarget[] apps) {
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
if (mSplitScreen != null) {
try {
- return mSplitScreen.onGoingToRecentsLegacy(cancel, apps);
+ return mSplitScreen.onGoingToRecentsLegacy(apps);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onGoingToRecentsLegacy");
}
@@ -691,6 +689,17 @@
return null;
}
+ public RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
+ if (mSplitScreen != null) {
+ try {
+ return mSplitScreen.onStartingSplitLegacy(apps);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onStartingSplitLegacy");
+ }
+ }
+ return null;
+ }
+
//
// One handed
//
@@ -847,8 +856,14 @@
}
}
- /** Clears the previously registered {@link IOnBackInvokedCallback}. */
- public void clearBackToLauncherCallback() {
+ /** Clears the previously registered {@link IOnBackInvokedCallback}.
+ *
+ * @param callback The previously registered callback instance.
+ */
+ public void clearBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ if (mBackToLauncherCallback != callback) {
+ return;
+ }
mBackToLauncherCallback = null;
if (mBackAnimation == null) {
return;
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4bb2400..54f457d 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -35,6 +35,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -176,9 +177,9 @@
((RecentsActivity) activityInterface.getCreatedActivity()).startHome();
return;
}
+
RemoteAnimationTarget[] nonAppTargets =
- SystemUiProxy.INSTANCE.getNoCreate()
- .onGoingToRecentsLegacy(false, nonHomeApps);
+ SystemUiProxy.INSTANCE.getNoCreate().onStartingSplitLegacy(nonHomeApps);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
&& activityInterface.getCreatedActivity() != null) {
@@ -191,6 +192,10 @@
RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */);
return;
}
+ } else if (nonAppTargets != null && nonAppTargets.length > 0) {
+ TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
+ RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */,
+ true /*shown*/, dividerAnimator -> dividerAnimator.start());
}
if (mController != null) {
if (mLastAppearedTaskTarget == null
@@ -234,9 +239,8 @@
// Allowing to pause Home if Home is top activity and Recents is not Home. So when user
// start home when recents animation is playing, the home activity can be resumed again
// to let the transition controller collect Home activity.
- ActivityManager.RunningTaskInfo rti = gestureState.getRunningTask();
- boolean homeIsOnTop = rti != null && rti.topActivity != null
- && rti.topActivity.equals(gestureState.getHomeIntent().getComponent());
+ CachedTaskInfo cti = gestureState.getRunningTask();
+ boolean homeIsOnTop = cti != null && cti.isHomeTask();
if (!homeIsOnTop) {
options.setTransientLaunch();
}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 300f085..3803f03 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -256,7 +256,7 @@
private BaseIconFactory getIconFactory() {
if (mIconFactory == null) {
mIconFactory = new BaseIconFactory(mContext,
- DisplayController.INSTANCE.get(mContext).getInfo().densityDpi,
+ DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(),
mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size));
}
return mIconFactory;
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 2d1f17c..3ef1332 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -46,6 +46,7 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.TaskShortcutFactory.SplitSelectSystemShortcut;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.OverviewActionsView;
@@ -118,8 +119,8 @@
* * There aren't at least 2 tasks in overview to show split options for
* * Device is in "Lock task mode"
* * The taskView to show split options for is the focused task AND we haven't started
- * scrolling in overview (if we haven't scrolled, there's a split overview action button so
- * we don't need this menu option)
+ * scrolling in overview (if we haven't scrolled, there's a split overview action button so
+ * we don't need this menu option)
*/
private static void addSplitOptions(List<SystemShortcut> outShortcuts,
BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
@@ -156,13 +157,15 @@
* Subclasses can attach any system listeners in this method, must be paired with
* {@link #removeListeners()}
*/
- public void initListeners() { }
+ public void initListeners() {
+ }
/**
* Subclasses should remove any system listeners in this method, must be paired with
* {@link #initListeners()}
*/
- public void removeListeners() { }
+ public void removeListeners() {
+ }
/** Note that these will be shown in order from top to bottom, if available for the task. */
private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
@@ -189,7 +192,7 @@
mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
mThumbnailView = taskThumbnailView;
mImageApi = new ImageActionsApi(
- mApplicationContext, mThumbnailView::getThumbnail);
+ mApplicationContext, mThumbnailView::getThumbnail);
}
protected T getActionsView() {
@@ -263,7 +266,8 @@
/**
* Gets the modal state system shortcut.
*/
- public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) {
+ public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo,
+ View original) {
return null;
}
@@ -277,9 +281,10 @@
* Gets the system shortcut for the screenshot that will be added to the task menu.
*/
public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
- ItemInfo iteminfo) {
- return new ScreenshotSystemShortcut(activity, iteminfo);
+ ItemInfo iteminfo, View originalView) {
+ return new ScreenshotSystemShortcut(activity, iteminfo, originalView);
}
+
/**
* Gets the task snapshot as it is displayed on the screen.
*
@@ -310,18 +315,29 @@
}
protected void showBlockedByPolicyMessage() {
+ ActivityContext activityContext = ActivityContext.lookupContext(
+ mThumbnailView.getContext());
+ String message = activityContext.getStringCache() != null
+ ? activityContext.getStringCache().disabledByAdminMessage
+ : mThumbnailView.getContext().getString(R.string.blocked_by_policy);
Toast.makeText(
mThumbnailView.getContext(),
- R.string.blocked_by_policy,
+ message,
Toast.LENGTH_LONG).show();
}
+ /** Called when the snapshot has updated its full screen drawing parameters. */
+ public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
+ }
+
private class ScreenshotSystemShortcut extends SystemShortcut {
private final BaseDraggingActivity mActivity;
- ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) {
- super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo);
+ ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo,
+ View originalView) {
+ super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo,
+ originalView);
mActivity = activity;
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index e731b79..e807e26 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -49,7 +49,6 @@
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -78,7 +77,7 @@
TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
taskContainer.getA11yNodeId()
);
- return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
+ return new AppInfo(activity, taskContainer.getItemInfo(), taskView, accessibilityInfo);
}
@Override
@@ -123,7 +122,7 @@
private final SplitPositionOption mSplitPositionOption;
public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
SplitPositionOption option) {
- super(option.iconResId, option.textResId, target, taskView.getItemInfo());
+ super(option.iconResId, option.textResId, target, taskView.getItemInfo(), taskView);
mTaskView = taskView;
mSplitPositionOption = option;
}
@@ -147,7 +146,8 @@
public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
LauncherEvent launcherEvent) {
- super(iconRes, textRes, activity, taskContainer.getItemInfo());
+ super(iconRes, textRes, activity, taskContainer.getItemInfo(),
+ taskContainer.getTaskView());
mLauncherEvent = launcherEvent;
mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskContainer.getTaskView();
@@ -320,7 +320,7 @@
public PinSystemShortcut(BaseDraggingActivity target,
TaskIdAttributeContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
- taskContainer.getItemInfo());
+ taskContainer.getItemInfo(), taskContainer.getTaskView());
mTaskView = taskContainer.getTaskView();
}
@@ -337,20 +337,23 @@
TaskShortcutFactory INSTALL = (activity, taskContainer) ->
InstantAppResolver.newInstance(activity).isInstantApp(activity,
- taskContainer.getTask().getTopComponent().getPackageName())
- ? new SystemShortcut.Install(activity, taskContainer.getItemInfo()) : null;
+ taskContainer.getTask().getTopComponent().getPackageName())
+ ? new SystemShortcut.Install(activity, taskContainer.getItemInfo(),
+ taskContainer.getTaskView()) : null;
TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
- WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo());
+ WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo(),
+ taskContainer.getTaskView());
TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
taskContainer.getThumbnailView().getTaskOverlay()
- .getScreenshotShortcut(activity, taskContainer.getItemInfo());
+ .getScreenshotShortcut(activity, taskContainer.getItemInfo(),
+ taskContainer.getTaskView());
TaskShortcutFactory MODAL = (activity, taskContainer) -> {
if (ENABLE_OVERVIEW_SELECTIONS.get()) {
- return taskContainer.getThumbnailView()
- .getTaskOverlay().getModalStateSystemShortcut(taskContainer.getItemInfo());
+ return taskContainer.getThumbnailView().getTaskOverlay().getModalStateSystemShortcut(
+ taskContainer.getItemInfo(), taskContainer.getTaskView());
}
return null;
};
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 29f2123..6179b81 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -38,7 +38,6 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -501,7 +500,7 @@
}
for (int i = 0; i < nonAppTargets.length; ++i) {
- final SurfaceControl leash = appTargets[i].leash;
+ final SurfaceControl leash = nonAppTargets[i].leash;
if (nonAppTargets[i].windowType == TYPE_DOCK_DIVIDER && leash != null) {
openingTargets.add(leash);
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
new file mode 100644
index 0000000..80bc329
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.ACTION_CHOOSER;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.util.SplitConfigurationOptions.StageType;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.launcher3.util.TraceHelper;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class tracked the top-most task and some 'approximate' task history to allow faster
+ * system state estimation during touch interaction
+ */
+public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
+
+ public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
+ new MainThreadInitializedObject<>(TopTaskTracker::new);
+
+ private static final int HISTORY_SIZE = 5;
+
+ // Ordered list with first item being the most recent task.
+ private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
+
+ private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
+ private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+ private TopTaskTracker(Context context) {
+ mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
+ mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
+
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
+ SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
+ }
+
+ @Override
+ public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
+ mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
+ mOrderedTaskList.addFirst(taskInfo);
+ if (mOrderedTaskList.size() >= HISTORY_SIZE) {
+ // If we grow in size, remove the last taskInfo which is not part of the split task.
+ Iterator<RunningTaskInfo> itr = mOrderedTaskList.descendingIterator();
+ while (itr.hasNext()) {
+ RunningTaskInfo info = itr.next();
+ if (info.taskId != taskInfo.taskId
+ && info.taskId != mMainStagePosition.taskId
+ && info.taskId != mSideStagePosition.taskId) {
+ itr.remove();
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.stagePosition = position;
+ } else {
+ mSideStagePosition.stagePosition = position;
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
+ // If task is not visible but we are tracking it, stop tracking it
+ if (!visible) {
+ if (mMainStagePosition.taskId == taskId) {
+ resetTaskId(mMainStagePosition);
+ } else if (mSideStagePosition.taskId == taskId) {
+ resetTaskId(mSideStagePosition);
+ } // else it's an un-tracked child
+ return;
+ }
+
+ // If stage has moved to undefined, stop tracking the task
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
+ resetTaskId(taskId == mMainStagePosition.taskId
+ ? mMainStagePosition : mSideStagePosition);
+ return;
+ }
+
+ if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+ mMainStagePosition.taskId = taskId;
+ } else {
+ mSideStagePosition.taskId = taskId;
+ }
+ }
+
+ private void resetTaskId(StagedSplitTaskPosition taskPosition) {
+ taskPosition.taskId = -1;
+ }
+
+ /**
+ * @return index 0 will be task in left/top position, index 1 in right/bottom position.
+ * Will return empty array if device is not in staged split
+ */
+ public int[] getRunningSplitTaskIds() {
+ if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
+ return new int[]{};
+ }
+ int[] out = new int[2];
+ if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ out[0] = mMainStagePosition.taskId;
+ out[1] = mSideStagePosition.taskId;
+ } else {
+ out[1] = mMainStagePosition.taskId;
+ out[0] = mSideStagePosition.taskId;
+ }
+ return out;
+ }
+
+
+ /**
+ * Returns the CachedTaskInfo for the top most task
+ */
+ @UiThread
+ public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
+ if (filterOnlyVisibleRecents) {
+ // Since we only know about the top most task, any filtering may not be applied on the
+ // cache. The second to top task may change while the top task is still the same.
+ RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
+ ActivityManagerWrapper.getInstance().getRunningTasks(true));
+ return new CachedTaskInfo(Arrays.asList(tasks));
+ }
+
+ if (mOrderedTaskList.isEmpty()) {
+ RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
+ ActivityManagerWrapper.getInstance().getRunningTasks(
+ false /* filterOnlyVisibleRecents */));
+ Collections.addAll(mOrderedTaskList, tasks);
+ }
+ return new CachedTaskInfo(new ArrayList<>(mOrderedTaskList));
+ }
+
+ /**
+ * Class to provide information about a task which can be safely cached and do not change
+ * during the lifecycle of the task.
+ */
+ public static class CachedTaskInfo {
+
+ private final RunningTaskInfo mTopTask;
+ private final List<RunningTaskInfo> mAllCachedTasks;
+
+ CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
+ mAllCachedTasks = allCachedTasks;
+ mTopTask = allCachedTasks.get(0);
+ }
+
+ public int getTaskId() {
+ return mTopTask.taskId;
+ }
+
+ /**
+ * Returns true if the root of the task chooser activity
+ */
+ public boolean isRootChooseActivity() {
+ return ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
+ }
+
+ /**
+ * Returns true if the given task holds an Assistant activity that is excluded from recents
+ */
+ public boolean isExcludedAssistant() {
+ return mTopTask.configuration.windowConfiguration
+ .getActivityType() == ACTIVITY_TYPE_ASSISTANT
+ && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ }
+
+ /**
+ * Returns true if this represents the HOME task
+ */
+ public boolean isHomeTask() {
+ return mTopTask.configuration.windowConfiguration
+ .getActivityType() == ACTIVITY_TYPE_HOME;
+ }
+
+ /**
+ * Returns {@link Task} array which can be used as a placeholder until the true object
+ * is loaded by the model
+ */
+ public Task[] getPlaceholderTasks() {
+ return new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
+ }
+
+ /**
+ * Returns {@link Task} array corresponding to the provided task ids which can be used as a
+ * placeholder until the true object is loaded by the model
+ */
+ public Task[] getPlaceholderTasks(int[] taskIds) {
+ Task[] result = new Task[taskIds.length];
+ for (int i = 0; i < taskIds.length; i++) {
+ final int index = i;
+ int taskId = taskIds[i];
+ mAllCachedTasks.forEach(rti -> {
+ if (rti.taskId == taskId) {
+ result[index] = Task.from(new TaskKey(rti), rti, false);
+ }
+ });
+ }
+ return result;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 3206825..f2583fb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import static android.content.Intent.ACTION_CHOOSER;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -41,7 +40,6 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.Service;
-import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -95,8 +93,6 @@
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.AssistantUtilities;
-import com.android.quickstep.util.LauncherSplitScreenListener;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.SplitScreenBounds;
@@ -314,6 +310,13 @@
public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
}
+
+ /**
+ * Sets the task id where gestures should be blocked
+ */
+ public void setGestureBlockedTaskId(int taskId) {
+ mDeviceState.setGestureBlockingTaskId(taskId);
+ }
}
private static boolean sConnected = false;
@@ -369,7 +372,6 @@
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
ProtoTracer.INSTANCE.get(this).add(this);
- LauncherSplitScreenListener.INSTANCE.get(this).init();
sConnected = true;
}
@@ -419,6 +421,9 @@
onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
onAssistantVisibilityChanged();
+ // Initialize the task tracker
+ TopTaskTracker.INSTANCE.get(this);
+
// Temporarily disable model preload
// new ModelPreload().start(this);
mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
@@ -531,7 +536,6 @@
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
- LauncherSplitScreenListener.INSTANCE.get(this).destroy();
mTaskbarManager.destroy();
sConnected = false;
super.onDestroy();
@@ -633,7 +637,7 @@
private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base,
GestureState gestureState, MotionEvent motionEvent) {
- return mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())
+ return mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())
? base
: new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat,
mDeviceState, motionEvent);
@@ -648,8 +652,8 @@
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
- gestureState.updateRunningTasks(TraceHelper.allowIpcs("getRunningTask.0",
- () -> mAM.getRunningTasks(false /* filterOnlyVisibleRecents */)));
+ gestureState.updateRunningTask(
+ TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false));
}
return gestureState;
}
@@ -696,7 +700,10 @@
// If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
// instead of going all the way home when a swipe up is detected.
- if (mDeviceState.isBubblesExpanded() || mDeviceState.isSystemUiDialogShowing()) {
+ // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
+ // expanded in the back. Make sure swipe up is not passed to bubbles in this case.
+ if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded())
+ || mDeviceState.isSystemUiDialogShowing()) {
base = new SysUiOverlayInputConsumer(
getBaseContext(), mDeviceState, mInputMonitorCompat);
}
@@ -743,17 +750,14 @@
// Use overview input consumer for sharesheets on top of home.
boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
&& gestureState.getRunningTask() != null
- && ACTION_CHOOSER.equals(gestureState.getRunningTask().baseIntent.getAction());
- if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
+ && gestureState.getRunningTask().isRootChooseActivity();
+ if (gestureState.getRunningTask() != null
+ && gestureState.getRunningTask().isExcludedAssistant()) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
- gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.assistant",
- () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
- ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
- ComponentName runningComponent =
- gestureState.getRunningTask().baseIntent.getComponent();
- forceOverviewInputConsumer =
- runningComponent != null && runningComponent.equals(homeComponent);
+ gestureState.updateRunningTask(TopTaskTracker.INSTANCE.get(this)
+ .getCachedTopTask(true /* filterOnlyVisibleRecents */));
+ forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()
@@ -770,7 +774,7 @@
|| forceOverviewInputConsumer) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
- } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
+ } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
return getDefaultInputConsumer();
} else {
return createOtherActivityInputConsumer(gestureState, event);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 5094d49..f68bbbc 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -112,7 +112,7 @@
RecentsState currentState = mActivity.getStateManager().getState();
if (isSplitSelectionState(state) && !isSplitSelectionState(currentState)) {
setter.add(mRecentsView.createSplitSelectInitAnimation(
- state.getTransitionDuration(mActivity)).buildAnim());
+ state.getTransitionDuration(mActivity, true /* isToState */)).buildAnim());
}
Pair<FloatProperty, FloatProperty> taskViewsFloat =
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index eda2c5a..3e68c7f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,7 +24,6 @@
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
@@ -50,7 +49,6 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
import java.util.ArrayList;
@@ -58,7 +56,7 @@
public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
implements StateListener<RecentsState> {
- private RunningTaskInfo mHomeTaskInfo;
+ private Task mHomeTask;
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -87,12 +85,12 @@
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
* from a foreground task.
*/
- public void onGestureAnimationStartOnHome(RunningTaskInfo[] homeTaskInfo,
+ public void onGestureAnimationStartOnHome(Task[] homeTask,
RotationTouchHelper rotationTouchHelper) {
// TODO(b/195607777) General fallback love, but this might be correct
// Home task should be defined as the front-most task info I think?
- mHomeTaskInfo = homeTaskInfo[0];
- onGestureAnimationStart(homeTaskInfo, rotationTouchHelper);
+ mHomeTask = homeTask[0];
+ onGestureAnimationStart(homeTask, rotationTouchHelper);
}
/**
@@ -105,8 +103,8 @@
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
TaskViewSimulator[] taskViewSimulators) {
super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
- if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
- TaskView tv = getTaskViewByTaskId(mHomeTaskInfo.taskId);
+ if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
+ TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
if (tv != null) {
PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
false /* dismissingForSplitSelection*/);
@@ -131,8 +129,8 @@
public void setCurrentTask(int runningTaskViewId) {
super.setCurrentTask(runningTaskViewId);
int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
- mHomeTaskInfo = null;
+ if (mHomeTask != null && mHomeTask.key.id != runningTaskId) {
+ mHomeTask = null;
setRunningTaskHidden(false);
}
}
@@ -140,26 +138,26 @@
@Nullable
@Override
protected TaskView getHomeTaskView() {
- return mHomeTaskInfo != null ? getTaskViewByTaskId(mHomeTaskInfo.taskId) : null;
+ return mHomeTask != null ? getTaskViewByTaskId(mHomeTask.key.id) : null;
}
@Override
- protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
- if (runningTaskInfos.length > 1) {
+ protected boolean shouldAddStubTaskView(Task[] runningTasks) {
+ if (runningTasks.length > 1) {
// can't be in split screen w/ home task
- return super.shouldAddStubTaskView(runningTaskInfos);
+ return super.shouldAddStubTaskView(runningTasks);
}
- RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
- if (mHomeTaskInfo != null && runningTaskInfo != null &&
- mHomeTaskInfo.taskId == runningTaskInfo.taskId
+ Task runningTask = runningTasks[0];
+ if (mHomeTask != null && runningTask != null
+ && mHomeTask.key.id == runningTask.key.id
&& getTaskViewCount() == 0 && mLoadPlanEverApplied) {
// Do not add a stub task if we are running over home with empty recents, so that we
// show the empty recents message instead of showing a stub task and later removing it.
// Ignore empty task signal if applyLoadPlan has never run.
return false;
}
- return super.shouldAddStubTaskView(runningTaskInfos);
+ return super.shouldAddStubTaskView(runningTasks);
}
@Override
@@ -169,7 +167,7 @@
// track the index of the next task appropriately, as if we are switching on any other app.
// TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+ if (mHomeTask != null && mHomeTask.key.id == runningTaskId
&& !taskGroups.isEmpty()) {
// Check if the task list has running task
boolean found = false;
@@ -182,9 +180,7 @@
if (!found) {
ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
newList.addAll(taskGroups);
- newList.add(new GroupTask(
- Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false),
- null, null));
+ newList.add(new GroupTask(mHomeTask, null, null));
taskGroups = newList;
}
}
@@ -193,7 +189,7 @@
@Override
public void setRunningTaskHidden(boolean isHidden) {
- if (mHomeTaskInfo != null) {
+ if (mHomeTask != null) {
// Always keep the home task hidden
isHidden = true;
}
@@ -225,6 +221,9 @@
setOverviewStateEnabled(true);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.isFullScreen());
+ if (toState == MODAL_TASK) {
+ setOverviewSelectEnabled(true);
+ }
Log.d(BAD_STATE, "FRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
setFreezeViewVisibility(true);
}
@@ -240,6 +239,9 @@
Log.d(BAD_STATE, "FRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+ finalState);
setFreezeViewVisibility(false);
+ if (finalState != MODAL_TASK) {
+ setOverviewSelectEnabled(false);
+ }
if (isOverlayEnabled) {
runActionOnRemoteHandles(remoteTargetHandle ->
@@ -268,4 +270,9 @@
super.initiateSplitSelect(splitSelectSource);
mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
+
+ @Override
+ protected boolean canLaunchFullscreenTask() {
+ return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 9705bb6..77db6b7 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -77,7 +77,7 @@
}
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
return 250;
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index dd459f5..11f0ff3 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -84,6 +84,9 @@
public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
+ // Minimum angle of a gesture's coordinate where a release goes to overview.
+ public static final int OVERVIEW_MIN_DEGREES = 15;
+
private final RecentsAnimationDeviceState mDeviceState;
private final NavBarPosition mNavBarPosition;
private final TaskAnimationManager mTaskAnimationManager;
@@ -291,8 +294,9 @@
// the gesture (in which case mPassedPilferInputSlop starts as true).
boolean haveNotPassedSlopOnContinuedGesture =
!mPassedSlopOnThisGesture && mPassedPilferInputSlop;
+ double degrees = Math.toDegrees(Math.atan(upDist / horizontalDist));
boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
- || horizontalDist > upDist;
+ || degrees <= OVERVIEW_MIN_DEGREES;
if (!mPassedPilferInputSlop) {
if (passedSlop) {
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 5ef9a9b..a379aad 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -17,11 +17,11 @@
import static com.android.launcher3.Utilities.mapBoundToRange;
import static com.android.launcher3.Utilities.mapRange;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
import android.app.Activity;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -55,6 +55,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.Executors;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.GestureState;
import com.android.quickstep.TouchInteractionService.TISBinder;
@@ -78,6 +80,8 @@
private static final float HINT_BOTTOM_FACTOR = 1 - .94f;
+ private static final int MAX_SWIPE_DURATION = 350;
+
private TISBindHelper mTISBindHelper;
private TISBinder mBinder;
@@ -90,6 +94,8 @@
private LottieAnimationView mAnimatedBackground;
private Animator.AnimatorListener mBackgroundAnimatorListener;
+ private AnimatorPlaybackController mLauncherStartAnim = null;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -137,6 +143,10 @@
startBackgroundAnimation();
}
+ private void runOnUiHelperThread(Runnable runnable) {
+ Executors.UI_HELPER_EXECUTOR.execute(runnable);
+ }
+
private void startBackgroundAnimation() {
if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
VibrationEffect.Composition.PRIMITIVE_THUD)) {
@@ -145,22 +155,22 @@
new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
- mVibrator.vibrate(getVibrationEffect());
+ runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
}
@Override
public void onAnimationRepeat(Animator animation) {
- mVibrator.vibrate(getVibrationEffect());
+ runOnUiHelperThread(() -> mVibrator.vibrate(getVibrationEffect()));
}
@Override
public void onAnimationEnd(Animator animation) {
- mVibrator.cancel();
+ runOnUiHelperThread(mVibrator::cancel);
}
@Override
public void onAnimationCancel(Animator animation) {
- mVibrator.cancel();
+ runOnUiHelperThread(mVibrator::cancel);
}
};
}
@@ -224,8 +234,7 @@
if (!state.getHomeIntent().getComponent().getPackageName().equals(getPackageName())) {
return null;
}
- RunningTaskInfo rti = state.getRunningTask();
- if (rti == null || !rti.topActivity.equals(getComponentName())) {
+ if (state.getRunningTaskId() != getTaskId()) {
return null;
}
mSwipeProgress.updateValue(0);
@@ -234,11 +243,20 @@
private void onSwipeProgressUpdate() {
mBackground.setProgress(mSwipeProgress.value);
- float alpha = Utilities.mapBoundToRange(mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR,
- 1, 0, LINEAR);
+ float alpha = Utilities.mapBoundToRange(
+ mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR, 1, 0, LINEAR);
mContentView.setAlpha(alpha);
mContentView.setTranslationY((alpha - 1) * mSwipeUpShift);
+ if (mLauncherStartAnim == null) {
+ mLauncherStartAnim = mBinder.getTaskbarManager().createLauncherStartFromSuwAnim(
+ MAX_SWIPE_DURATION);
+ }
+ if (mLauncherStartAnim != null) {
+ mLauncherStartAnim.setPlayFraction(Utilities.mapBoundToRange(
+ mSwipeProgress.value, 0, 1, 0, 1, FAST_OUT_SLOW_IN));
+ }
+
if (alpha == 0f) {
mAnimatedBackground.pauseAnimation();
} else if (!mAnimatedBackground.isAnimating()) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index b686505..35d9f22 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -33,17 +33,22 @@
}
@Override
- public Integer getIntroductionTitle() {
+ public int getIntroductionTitle() {
return R.string.back_gesture_intro_title;
}
@Override
- public Integer getIntroductionSubtitle() {
+ public int getIntroductionSubtitle() {
return R.string.back_gesture_intro_subtitle;
}
@Override
- public Integer getSuccessFeedbackSubtitle() {
+ public int getSpokenIntroductionSubtitle() {
+ return R.string.back_gesture_spoken_intro_subtitle;
+ }
+
+ @Override
+ public int getSuccessFeedbackSubtitle() {
return mTutorialFragment.isAtFinalStep()
? R.string.back_gesture_feedback_complete_without_follow_up
: R.string.back_gesture_feedback_complete_with_overview_follow_up;
@@ -117,7 +122,22 @@
mTutorialFragment.closeTutorial();
}
} else if (mTutorialType == BACK_NAVIGATION) {
- showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+ switch (result) {
+ case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case HOME_OR_OVERVIEW_CANCELLED:
+ showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ case HOME_GESTURE_COMPLETED:
+ case OVERVIEW_GESTURE_COMPLETED:
+ case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+ case ASSISTANT_COMPLETED:
+ case ASSISTANT_NOT_STARTED_BAD_ANGLE:
+ case ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT:
+ default:
+ showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 7daac81..bf7023c 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -31,9 +31,11 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.interaction.TutorialController.TutorialType;
+import com.android.quickstep.util.TISBindHelper;
-import java.util.List;
+import java.util.Arrays;
/** Shows the gesture interactive sandbox in full screen mode. */
public class GestureSandboxActivity extends FragmentActivity {
@@ -52,6 +54,9 @@
private SharedPreferences mSharedPrefs;
private StatsLogManager mStatsLogManager;
+ private TISBindHelper mTISBindHelper;
+ private TISBinder mBinder;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -69,6 +74,8 @@
getSupportFragmentManager().beginTransaction()
.add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
+
+ mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
@Override
@@ -206,7 +213,37 @@
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
getWindow().setSystemGestureExclusionRects(
- List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
+ Arrays.asList(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
}
}
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateServiceState(true);
+ }
+
+ private void onTISConnected(TISBinder binder) {
+ mBinder = binder;
+ updateServiceState(isResumed());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ updateServiceState(false);
+ }
+
+ private void updateServiceState(boolean isEnabled) {
+ if (mBinder != null) {
+ mBinder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mTISBindHelper.onDestroy();
+ updateServiceState(false);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 6254313..f519d50 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -32,17 +32,22 @@
}
@Override
- public Integer getIntroductionTitle() {
+ public int getIntroductionTitle() {
return R.string.home_gesture_intro_title;
}
@Override
- public Integer getIntroductionSubtitle() {
+ public int getIntroductionSubtitle() {
return R.string.home_gesture_intro_subtitle;
}
@Override
- public Integer getSuccessFeedbackSubtitle() {
+ public int getSpokenIntroductionSubtitle() {
+ return R.string.home_gesture_spoken_intro_subtitle;
+ }
+
+ @Override
+ public int getSuccessFeedbackSubtitle() {
return mTutorialFragment.isAtFinalStep()
? R.string.home_gesture_feedback_complete_without_follow_up
: R.string.home_gesture_feedback_complete_with_follow_up;
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 09640c6..6b016ce 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -42,17 +42,22 @@
}
@Override
- public Integer getIntroductionTitle() {
+ public int getIntroductionTitle() {
return R.string.overview_gesture_intro_title;
}
@Override
- public Integer getIntroductionSubtitle() {
+ public int getIntroductionSubtitle() {
return R.string.overview_gesture_intro_subtitle;
}
@Override
- public Integer getSuccessFeedbackSubtitle() {
+ public int getSpokenIntroductionSubtitle() {
+ return R.string.overview_gesture_spoken_intro_subtitle;
+ }
+
+ @Override
+ public int getSuccessFeedbackSubtitle() {
return mTutorialFragment.getNumSteps() > 1 && mTutorialFragment.isAtFinalStep()
? R.string.overview_gesture_feedback_complete_with_follow_up
: R.string.overview_gesture_feedback_complete_without_follow_up;
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 6a8894e..fa7d848 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -62,7 +62,7 @@
abstract class TutorialController implements BackGestureAttemptCallback,
NavBarGestureAttemptCallback {
- private static final String TAG = "TutorialController";
+ private static final String LOG_TAG = "TutorialController";
private static final float FINGER_DOT_VISIBLE_ALPHA = 0.7f;
private static final float FINGER_DOT_SMALL_SCALE = 0.7f;
@@ -217,18 +217,23 @@
}
@StringRes
- public Integer getIntroductionTitle() {
- return null;
+ public int getIntroductionTitle() {
+ return NO_ID;
}
@StringRes
- public Integer getIntroductionSubtitle() {
- return null;
+ public int getIntroductionSubtitle() {
+ return NO_ID;
}
@StringRes
- public Integer getSuccessFeedbackSubtitle() {
- return null;
+ public int getSpokenIntroductionSubtitle() {
+ return NO_ID;
+ }
+
+ @StringRes
+ public int getSuccessFeedbackSubtitle() {
+ return NO_ID;
}
void showFeedback() {
@@ -247,7 +252,16 @@
* Show feedback reflecting a successful gesture attempt.
**/
void showSuccessFeedback() {
- showFeedback(getSuccessFeedbackSubtitle(), true);
+ int successSubtitleResId = getSuccessFeedbackSubtitle();
+ if (successSubtitleResId == NO_ID) {
+ // Allow crash since this should never be reached with a tutorial controller used in
+ // production.
+ Log.e(LOG_TAG,
+ "Cannot show success feedback for tutorial step: " + mTutorialType
+ + ", no success feedback subtitle",
+ new IllegalStateException());
+ }
+ showFeedback(successSubtitleResId, true);
}
/**
@@ -269,6 +283,7 @@
isGestureSuccessful
? R.string.gesture_tutorial_nice : R.string.gesture_tutorial_try_again,
subtitleResId,
+ NO_ID,
isGestureSuccessful,
false);
}
@@ -276,6 +291,7 @@
void showFeedback(
int titleResId,
int subtitleResId,
+ int spokenSubtitleResId,
boolean isGestureSuccessful,
boolean useGestureAnimationDelay) {
mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
@@ -287,7 +303,10 @@
mFeedbackTitleView.setText(titleResId);
TextView subtitle =
mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle);
- subtitle.setText(subtitleResId);
+ subtitle.setText(spokenSubtitleResId == NO_ID
+ ? mContext.getText(subtitleResId)
+ : Utilities.wrapForTts(
+ mContext.getText(subtitleResId), mContext.getString(spokenSubtitleResId)));
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
showActionButton();
@@ -580,7 +599,7 @@
packageManager.getApplicationInfo(
PIXEL_TIPS_APP_PACKAGE_NAME, PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG,
+ Log.e(LOG_TAG,
"Could not find app label for package name: "
+ PIXEL_TIPS_APP_PACKAGE_NAME
+ ". Defaulting to 'Pixel Tips.'",
@@ -593,7 +612,7 @@
subtitleTextView.setText(
mContext.getString(R.string.skip_tutorial_dialog_subtitle, tipsAppName));
} else {
- Log.w(TAG, "No subtitle view in the skip tutorial dialog to update.");
+ Log.w(LOG_TAG, "No subtitle view in the skip tutorial dialog to update.");
}
Button cancelButton = (Button) contentView.findViewById(
@@ -602,7 +621,7 @@
cancelButton.setOnClickListener(
v -> tutorialDialog.dismiss());
} else {
- Log.w(TAG, "No cancel button in the skip tutorial dialog to update.");
+ Log.w(LOG_TAG, "No cancel button in the skip tutorial dialog to update.");
}
Button confirmButton = contentView.findViewById(
@@ -613,7 +632,7 @@
tutorialDialog.dismiss();
});
} else {
- Log.w(TAG, "No confirm button in the skip tutorial dialog to update.");
+ Log.w(LOG_TAG, "No confirm button in the skip tutorial dialog to update.");
}
tutorialDialog.getWindow().setBackgroundDrawable(
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 33e800d..1599c8c 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.interaction;
+import static android.view.View.NO_ID;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
@@ -211,13 +213,31 @@
if (isGestureComplete()) {
mTutorialController.showSuccessFeedback();
} else if (!mIntroductionShown) {
- Integer introTileStringResId = mTutorialController.getIntroductionTitle();
- Integer introSubtitleResId = mTutorialController.getIntroductionSubtitle();
- if (introTileStringResId != null && introSubtitleResId != null) {
- mTutorialController.showFeedback(
- introTileStringResId, introSubtitleResId, false, true);
- mIntroductionShown = true;
+ int introTitleResId = mTutorialController.getIntroductionTitle();
+ int introSubtitleResId = mTutorialController.getIntroductionSubtitle();
+ if (introTitleResId == NO_ID) {
+ // Allow crash since this should never be reached with a tutorial controller used in
+ // production.
+ Log.e(LOG_TAG,
+ "Cannot show introduction feedback for tutorial step: " + mTutorialType
+ + ", no introduction feedback title",
+ new IllegalStateException());
}
+ if (introTitleResId == NO_ID) {
+ // Allow crash since this should never be reached with a tutorial controller used in
+ // production.
+ Log.e(LOG_TAG,
+ "Cannot show introduction feedback for tutorial step: " + mTutorialType
+ + ", no introduction feedback subtitle",
+ new IllegalStateException());
+ }
+ mTutorialController.showFeedback(
+ introTitleResId,
+ introSubtitleResId,
+ mTutorialController.getSpokenIntroductionSubtitle(),
+ false,
+ true);
+ mIntroductionShown = true;
}
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 13007ea..85ef6cb 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -20,6 +20,7 @@
import static androidx.core.util.Preconditions.checkState;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.ALL_APPS_CONTAINER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
@@ -92,6 +93,7 @@
private static final int FOLDER_HIERARCHY_OFFSET = 100;
private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300;
+ private static final int ALL_APPS_HIERARCHY_OFFSET = 400;
/**
* Flags for converting SearchAttribute to integer value.
@@ -128,7 +130,7 @@
if (IS_VERBOSE) {
Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
}
- if (!Utilities.ATLEAST_R) {
+ if (!Utilities.ATLEAST_R || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return;
}
SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
@@ -413,6 +415,10 @@
consumer.consume(event, atomInfo);
}
+ // TODO: remove this when b/231648228 is fixed.
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ return;
+ }
SysUiStatsLog.write(
SysUiStatsLog.LAUNCHER_EVENT,
SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
@@ -508,6 +514,9 @@
}
private static int getCardinality(LauncherAtom.ItemInfo info) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ return 0;
+ }
switch (info.getContainerInfo().getContainerCase()) {
case PREDICTED_HOTSEAT_CONTAINER:
return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
@@ -623,6 +632,9 @@
}
private static int getHierarchy(LauncherAtom.ItemInfo info) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ return 0;
+ }
if (info.getContainerInfo().getContainerCase() == FOLDER) {
return info.getContainerInfo().getFolder().getParentContainerCase().getNumber()
+ FOLDER_HIERARCHY_OFFSET;
@@ -632,6 +644,9 @@
} else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) {
return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber()
+ EXTENDED_CONTAINERS_HIERARCHY_OFFSET;
+ } else if (info.getContainerInfo().getContainerCase() == ALL_APPS_CONTAINER) {
+ return info.getContainerInfo().getAllAppsContainer().getParentContainerCase()
+ .getNumber() + ALL_APPS_HIERARCHY_OFFSET;
} else {
return info.getContainerInfo().getContainerCase().getNumber();
}
@@ -660,6 +675,9 @@
}
private static int getSearchAttributes(LauncherAtom.ItemInfo info) {
+ if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+ return 0;
+ }
ContainerInfo containerInfo = info.getContainerInfo();
if (containerInfo.getContainerCase() == EXTENDED_CONTAINERS
&& containerInfo.getExtendedContainers().getContainerCase()
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
deleted file mode 100644
index 336f7d1..0000000
--- a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
-
-import android.annotation.TargetApi;
-import android.app.TaskInfo;
-import android.content.Intent;
-import android.os.Build;
-
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-/**
- * Utility class for interacting with the Assistant.
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public final class AssistantUtilities {
-
- /** Returns true if an Assistant activity that is excluded from recents is running. */
- public static boolean isExcludedAssistantRunning() {
- return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask());
- }
-
- /** Returns true if the given task holds an Assistant activity that is excluded from recents. */
- public static boolean isExcludedAssistant(TaskInfo info) {
- return info != null
- && info.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_ASSISTANT
- && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
- }
-
- private AssistantUtilities() {}
-}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
deleted file mode 100644
index affe625..0000000
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-
-import android.content.Context;
-import android.os.IBinder;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StageType;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
-import com.android.quickstep.SystemUiProxy;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
-
-/**
- * Listeners for system wide split screen position and stage changes.
- *
- * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in
- * staged split.
- */
-public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
-
- public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
- new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
-
- private static final int[] EMPTY_ARRAY = {};
-
- private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
- private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
-
- /**
- * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
- * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
- * home - in that case the task list does not unfreeze immediately after the gesture, so it's
- * done via {@link #notifySwipingToHome()}.
- *
- * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
- * page, so user can quickswitch back to a grouped task.
- */
- private int[] mPersistentGroupedIds;
-
- public LauncherSplitScreenListener(Context context) {
- mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
- mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
- }
-
- /** Also call {@link #destroy()} when done. */
- public void init() {
- SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
- }
-
- public void destroy() {
- SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
- }
-
- /**
- * @return index 0 will be task in left/top position, index 1 in right/bottom position.
- * Will return empty array if device is not in staged split
- */
- public int[] getRunningSplitTaskIds() {
- if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
- return new int[]{};
- }
- int[] out = new int[2];
- if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
- out[0] = mMainStagePosition.taskId;
- out[1] = mSideStagePosition.taskId;
- } else {
- out[1] = mMainStagePosition.taskId;
- out[0] = mSideStagePosition.taskId;
- }
- return out;
- }
-
- @Override
- public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
- if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
- mMainStagePosition.stagePosition = position;
- } else {
- mSideStagePosition.stagePosition = position;
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- // If task is not visible but we are tracking it, stop tracking it
- if (!visible) {
- if (mMainStagePosition.taskId == taskId) {
- resetTaskId(mMainStagePosition);
- } else if (mSideStagePosition.taskId == taskId) {
- resetTaskId(mSideStagePosition);
- } // else it's an un-tracked child
- return;
- }
-
- // If stage has moved to undefined, stop tracking the task
- if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
- resetTaskId(taskId == mMainStagePosition.taskId ?
- mMainStagePosition : mSideStagePosition);
- return;
- }
-
- if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
- mMainStagePosition.taskId = taskId;
- } else {
- mSideStagePosition.taskId = taskId;
- }
- }
-
- private void resetTaskId(StagedSplitTaskPosition taskPosition) {
- taskPosition.taskId = -1;
- }
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 5cf4f0b..3cec1a4 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -82,7 +82,7 @@
// WorkspaceRevealAnim handles the depth, so don't interfere.
config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
}
- config.duration = startState.getTransitionDuration(mLauncher);
+ config.duration = startState.getTransitionDuration(mLauncher, false /* isToState */);
AnimatorSet stateAnim = stateManager.createAtomicAnimation(
startState, NORMAL, config);
stateAnim.addListener(new AnimationSuccessListener() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 1631be0..6038a22 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -307,6 +307,7 @@
private void initMultipleOrientationListeners() {
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
+ updateAutoRotateSetting();
}
private void destroyMultipleOrientationListeners() {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index ee69fc3..2502359 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -73,6 +73,7 @@
private Intent mInitialTaskIntent;
private int mInitialTaskId = INVALID_TASK_ID;
private int mSecondTaskId = INVALID_TASK_ID;
+ private String mSecondTaskPackageName;
private boolean mRecentsAnimationRunning;
/** If not null, this is the TaskView we want to launch from */
@Nullable
@@ -103,15 +104,15 @@
}
/**
- * To be called after second task selected
+ * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
+ * to be launched. Call after launcher side animations are complete.
*/
- public void setSecondTask(Task task, Consumer<Boolean> callback) {
- mSecondTaskId = task.key.id;
+ public void launchSplitTasks(Consumer<Boolean> callback) {
final Intent fillInIntent;
if (mInitialTaskIntent != null) {
fillInIntent = new Intent();
if (TextUtils.equals(mInitialTaskIntent.getComponent().getPackageName(),
- task.getTopComponent().getPackageName())) {
+ mSecondTaskPackageName)) {
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
}
} else {
@@ -124,6 +125,18 @@
callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
}
+
+ /**
+ * To be called as soon as user selects the second task (even if animations aren't complete)
+ * @param task The second task that will be launched.
+ */
+ public void setSecondTask(Task task) {
+ mSecondTaskId = task.key.id;
+ if (mInitialTaskIntent != null) {
+ mSecondTaskPackageName = task.getTopComponent().getPackageName();
+ }
+ }
+
/**
* To be called when we want to launch split pairs from an existing GroupedTaskView.
*/
@@ -303,7 +316,18 @@
* chosen
*/
public boolean isSplitSelectActive() {
- return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null)
- && mSecondTaskId == INVALID_TASK_ID;
+ return isInitialTaskIntentSet() && mSecondTaskId == INVALID_TASK_ID;
+ }
+
+ /**
+ * @return {@code true} if the first and second task have been chosen and split is waiting to
+ * be launched
+ */
+ public boolean isBothSplitAppsConfirmed() {
+ return isInitialTaskIntentSet() && mSecondTaskId != INVALID_TASK_ID;
+ }
+
+ private boolean isInitialTaskIntentSet() {
+ return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
index 3b1c150..4757d4b 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -53,6 +53,7 @@
mTargetViewRootImpl = targetView.getViewRootImpl();
mBarrierSurfaceControl = mTargetViewRootImpl.getSurfaceControl();
mApplyHandler = new Handler(this::onApplyMessage);
+ setCanRelease(true);
}
protected boolean onApplyMessage(Message msg) {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 54420de..c980d1e 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -196,6 +196,13 @@
0, 1, ACCEL);
animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
1, 0, DEACCEL_3);
+ } else if (isStagedTask) {
+ // Fade in the placeholder view when split is initiated from homescreen / all apps
+ // icons.
+ if (mSplitPlaceholderView.getAlpha() == 0) {
+ animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
+ 0.3f, 1, ACCEL);
+ }
}
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 955fffc..244a794 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -26,6 +26,7 @@
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
import java.util.function.Consumer;
@@ -171,8 +172,14 @@
RunnableList endCallback = new RunnableList();
RecentsView recentsView = getRecentsView();
// Callbacks run from remote animation when recents animation not currently running
+ InteractionJankMonitorWrapper.begin(this,
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
- success -> endCallback.executeAllAndDestroy(),
+ success -> {
+ endCallback.executeAllAndDestroy();
+ InteractionJankMonitorWrapper.end(
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ },
false /* freezeTaskList */);
// Callbacks get run from recentsView for case when recents animation already running
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 8d717d2..306ebd7 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -100,6 +100,9 @@
setOverviewStateEnabled(toState.overviewUi);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
+ if (toState == OVERVIEW_MODAL_TASK) {
+ setOverviewSelectEnabled(true);
+ }
Log.d(BAD_STATE, "LRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
setFreezeViewVisibility(true);
}
@@ -115,6 +118,9 @@
Log.d(BAD_STATE, "LRV onStateTransitionComplete setFreezeVisibility=false, finalState="
+ finalState);
setFreezeViewVisibility(false);
+ if (finalState != OVERVIEW_MODAL_TASK) {
+ setOverviewSelectEnabled(false);
+ }
if (isOverlayEnabled) {
runActionOnRemoteHandles(remoteTargetHandle ->
@@ -181,4 +187,9 @@
super.initiateSplitSelect(splitSelectSource);
mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
+
+ @Override
+ protected boolean canLaunchFullscreenTask() {
+ return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 6b15807..1c4e497 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,8 +16,6 @@
package com.android.quickstep.views;
-import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -58,7 +56,6 @@
HIDDEN_NON_ZERO_ROTATION,
HIDDEN_NO_TASKS,
HIDDEN_NO_RECENTS,
- HIDDEN_FOCUSED_SCROLL,
HIDDEN_SPLIT_SCREEN})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionsHiddenFlags { }
@@ -66,8 +63,7 @@
public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
public static final int HIDDEN_NO_TASKS = 1 << 1;
public static final int HIDDEN_NO_RECENTS = 1 << 2;
- public static final int HIDDEN_FOCUSED_SCROLL = 1 << 3;
- public static final int HIDDEN_SPLIT_SCREEN = 1 << 4;
+ public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
@IntDef(flag = true, value = {
DISABLED_SCROLLING,
@@ -99,6 +95,7 @@
@Nullable
protected DeviceProfile mDp;
+ private final Rect mTaskSize = new Rect();
public OverviewActionsView(Context context) {
this(context, null);
@@ -155,7 +152,7 @@
public void setInsets(Rect insets) {
mInsets.set(insets);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
- updatePaddingAndTranslations();
+ updatePadding();
}
public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -199,12 +196,13 @@
}
/**
- * Aligns OverviewActionsView vertically with and offsets horizontal position based on
- * 3 button nav container in taskbar.
+ * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
*/
- private void updatePaddingAndTranslations() {
- boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
- DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS;
+ private void updatePadding() {
+ if (mDp == null) {
+ return;
+ }
+ boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && !mDp.isGestureMode;
if (alignFor3ButtonTaskbar) {
// Add extra horizontal spacing
int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
@@ -213,20 +211,8 @@
} else {
setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0);
}
-
- // Align vertically, using taskbar height + mDp.taskbarOffsetY() to estimate where
- // the button nav top is.
- int marginBottom = getOverviewActionsBottomMarginPx(
- DisplayController.getNavigationMode(getContext()), mDp);
- int actionsTop =
- (mDp.heightPx - marginBottom - mInsets.bottom) - mDp.overviewActionsHeight;
- int navTop = mDp.heightPx - (mDp.taskbarSize + mDp.getTaskbarOffsetY());
- int transY = navTop - actionsTop + ((mDp.taskbarSize - mDp.overviewActionsHeight) / 2);
- setTranslationY(transY);
} else {
setPadding(mInsets.left, 0, mInsets.right, 0);
- setTranslationX(0);
- setTranslationY(0);
}
}
@@ -238,15 +224,34 @@
LayoutParams actionParams = (LayoutParams) findViewById(
R.id.action_buttons).getLayoutParams();
actionParams.setMargins(
- actionParams.leftMargin, getOverviewActionsTopMarginPx(mode, mDp),
- actionParams.rightMargin, getOverviewActionsBottomMarginPx(mode, mDp));
+ actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
+ actionParams.rightMargin, getBottomMargin());
+ }
+
+ private int getBottomMargin() {
+ if (mDp == null) {
+ return 0;
+ }
+
+ if (mDp.isVerticalBarLayout()) {
+ return mDp.getInsets().bottom;
+ }
+
+ if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
+ return mDp.getOverviewActionsClaimedSpaceBelow();
+ }
+
+ // Align to bottom of task Rect.
+ return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
+ - mDp.overviewActionsHeight;
}
/**
- * Set the device profile for this view to draw with.
+ * Updates device profile and task size for this view to draw with.
*/
- public void setDp(DeviceProfile dp) {
+ public void updateDimension(DeviceProfile dp, Rect taskSize) {
mDp = dp;
+ mTaskSize.set(taskSize);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -270,36 +275,4 @@
mSplitButton.setVisibility(visible ? VISIBLE : GONE);
findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
}
-
- /** Get the top margin associated with the action buttons in Overview. */
- public static int getOverviewActionsTopMarginPx(NavigationMode mode, DeviceProfile dp) {
- // In vertical bar, use the smaller task margin for the top regardless of mode
- if (dp.isVerticalBarLayout()) {
- return dp.overviewTaskMarginPx;
- }
-
- if (mode == NavigationMode.THREE_BUTTONS) {
- return dp.overviewActionsMarginThreeButtonPx;
- }
-
- return dp.overviewActionsTopMarginGesturePx;
- }
-
- /** Get the bottom margin associated with the action buttons in Overview. */
- public static int getOverviewActionsBottomMarginPx(NavigationMode mode, DeviceProfile dp) {
- int inset = dp.getInsets().bottom;
-
- if (dp.isVerticalBarLayout()) {
- return inset;
- }
-
- // Actions button will be aligned with nav buttons in updatePaddingAndTranslations().
- if (mode == NavigationMode.THREE_BUTTONS) {
- return dp.overviewActionsMarginThreeButtonPx + inset;
- }
-
- // There is no bottom inset when taskbar is present, use stashed taskbar as padding instead.
- return dp.overviewActionsBottomMarginGesturePx
- + (dp.isTaskbarPresent ? dp.stashedTaskbarSize : inset);
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c6d26a1..b634518 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -64,8 +64,8 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.LocusId;
import android.content.res.Configuration;
@@ -106,6 +106,7 @@
import android.widget.ListView;
import android.widget.OverScroller;
import android.widget.Toast;
+import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -159,6 +160,7 @@
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
@@ -171,9 +173,9 @@
import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@@ -376,6 +378,8 @@
// OverScroll constants
private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
+ private static final int DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION = 300;
+
private static final int DISMISS_TASK_DURATION = 300;
private static final int ADDITION_TASK_DURATION = 200;
private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
@@ -453,6 +457,7 @@
protected boolean mFreezeViewVisibility;
private boolean mOverviewGridEnabled;
private boolean mOverviewFullscreenEnabled;
+ private boolean mOverviewSelectEnabled;
private float mAdjacentPageHorizontalOffset = 0;
protected float mTaskViewsSecondaryTranslation = 0;
@@ -649,6 +654,8 @@
private TaskView mMovingTaskView;
private OverviewActionsView mActionsView;
+ private ObjectAnimator mActionsViewAlphaAnimator;
+ private float mActionsViewAlphaAnimatorFinalValue;
private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
new MultiWindowModeChangedListener() {
@@ -878,6 +885,14 @@
return mSplitSelectStateController.isSplitSelectActive();
}
+ /**
+ * See overridden implementations
+ * @return {@code true} if child TaskViews can be launched when user taps on them
+ */
+ protected boolean canLaunchFullscreenTask() {
+ return true;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -931,7 +946,7 @@
mHasVisibleTaskData.delete(i);
}
if (child instanceof GroupedTaskView) {
- mGroupedTaskViewPool.recycle((GroupedTaskView)taskView);
+ mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
} else {
mTaskViewPool.recycle(taskView);
}
@@ -1134,6 +1149,11 @@
return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
}
+ private boolean isFocusedTaskInExpectedScrollPosition() {
+ TaskView focusedTask = getFocusedTaskView();
+ return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask));
+ }
+
/**
* Returns a {@link TaskView} that has taskId matching {@code taskId} or null if no match.
*/
@@ -1180,13 +1200,15 @@
@Override
protected void onPageBeginTransition() {
super.onPageBeginTransition();
- mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
+ if (!mActivity.getDeviceProfile().isTablet) {
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
+ }
}
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
- if (isClearAllHidden()) {
+ if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
}
if (getNextPage() > 0) {
@@ -1386,6 +1408,9 @@
if (taskGroups == null || taskGroups.isEmpty()) {
removeTasksViewsAndClearAllButton();
onTaskStackUpdated();
+ // With all tasks removed, touch handling in PagedView is disabled and we need to reset
+ // touch state or otherwise values will be obsolete.
+ resetTouchState();
return;
}
@@ -1624,11 +1649,11 @@
// Propagate DeviceProfile change event.
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
- mActionsView.setDp(dp);
mOrientationState.setDeviceProfile(dp);
// Update RecentsView and TaskView's DeviceProfile dependent layout.
updateOrientationHandler();
+ mActionsView.updateDimension(dp, mLastComputedTaskSize);
}
private void updateOrientationHandler() {
@@ -1693,7 +1718,7 @@
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
- mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+ mSizeStrategy.calculateGridSize(mActivity.getDeviceProfile(),
mLastComputedGridSize);
mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedGridTaskSize, mOrientationHandler);
@@ -1751,7 +1776,8 @@
* Returns the size of task selected to enter modal state.
*/
public Point getSelectedTaskSize() {
- mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect);
+ mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(),
+ mTempRect);
return new Point(mTempRect.width(), mTempRect.height());
}
@@ -1793,16 +1819,24 @@
}
private void updateActionsViewFocusedScroll() {
- boolean hiddenFocusedScroll;
if (showAsGrid()) {
- TaskView focusedTaskView = getFocusedTaskView();
- hiddenFocusedScroll = focusedTaskView == null
- || !isTaskInExpectedScrollPosition(indexOfChild(focusedTaskView));
- } else {
- hiddenFocusedScroll = false;
+ float actionsViewAlphaValue = isFocusedTaskInExpectedScrollPosition() ? 1 : 0;
+ // If animation is already in progress towards the same end value, do not restart.
+ if (mActionsViewAlphaAnimator == null || !mActionsViewAlphaAnimator.isStarted()
+ || (mActionsViewAlphaAnimator.isStarted()
+ && mActionsViewAlphaAnimatorFinalValue != actionsViewAlphaValue)) {
+ animateActionsViewAlpha(actionsViewAlphaValue,
+ DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION);
+ }
}
- mActionsView.updateHiddenFlags(OverviewActionsView.HIDDEN_FOCUSED_SCROLL,
- hiddenFocusedScroll);
+ }
+
+ private void animateActionsViewAlpha(float alphaValue, long duration) {
+ mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(
+ mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, alphaValue);
+ mActionsViewAlphaAnimatorFinalValue = alphaValue;
+ mActionsViewAlphaAnimator.setDuration(duration);
+ mActionsViewAlphaAnimator.start();
}
/**
@@ -1821,17 +1855,18 @@
if (!mActivity.getDeviceProfile().isTablet) {
return super.getDestinationPage(scaledScroll);
}
-
- final int childCount = getChildCount();
- if (mPageScrolls == null || childCount != mPageScrolls.length) {
- return -1;
+ if (!pageScrollsInitialized()) {
+ Log.e(TAG,
+ "Cannot get destination page: RecentsView not properly initialized",
+ new IllegalStateException());
+ return INVALID_PAGE;
}
// When in tablet with variable task width, return the page which scroll is closest to
// screenStart instead of page nearest to center of screen.
int minDistanceFromScreenStart = Integer.MAX_VALUE;
- int minDistanceFromScreenStartIndex = -1;
- for (int i = 0; i < childCount; ++i) {
+ int minDistanceFromScreenStartIndex = INVALID_PAGE;
+ for (int i = 0; i < getChildCount(); ++i) {
int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
if (distanceFromScreenStart < minDistanceFromScreenStart) {
minDistanceFromScreenStart = distanceFromScreenStart;
@@ -2072,8 +2107,8 @@
/**
* Called when a gesture from an app is starting.
*/
- public void onGestureAnimationStart(RunningTaskInfo[] runningTaskInfo,
- RotationTouchHelper rotationTouchHelper) {
+ public void onGestureAnimationStart(
+ Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
mGestureActive = true;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -2084,7 +2119,7 @@
updateSizeAndPadding();
}
- showCurrentTask(runningTaskInfo);
+ showCurrentTask(runningTasks);
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
@@ -2200,10 +2235,10 @@
/**
* Returns true if we should add a stub taskView for the running task id
*/
- protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
- if (runningTaskInfos.length > 1) {
- TaskView primaryTaskView = getTaskViewByTaskId(runningTaskInfos[0].taskId);
- TaskView secondaryTaskView = getTaskViewByTaskId(runningTaskInfos[1].taskId);
+ protected boolean shouldAddStubTaskView(Task[] runningTasks) {
+ if (runningTasks.length > 1) {
+ TaskView primaryTaskView = getTaskViewByTaskId(runningTasks[0].key.id);
+ TaskView secondaryTaskView = getTaskViewByTaskId(runningTasks[1].key.id);
int leftTopTaskViewId =
(primaryTaskView == null) ? -1 : primaryTaskView.getTaskViewId();
int rightBottomTaskViewId =
@@ -2211,8 +2246,8 @@
// Add a new stub view if both taskIds don't match any taskViews
return leftTopTaskViewId != rightBottomTaskViewId || leftTopTaskViewId == -1;
}
- RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
- return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null;
+ Task runningTaskInfo = runningTasks[0];
+ return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.key.id) == null;
}
/**
@@ -2221,21 +2256,16 @@
* All subsequent calls to reload will keep the task as the first item until {@link #reset()}
* is called. Also scrolls the view to this task.
*/
- private void showCurrentTask(RunningTaskInfo[] runningTaskInfo) {
+ private void showCurrentTask(Task[] runningTasks) {
int runningTaskViewId = -1;
- boolean needGroupTaskView = runningTaskInfo.length > 1;
- RunningTaskInfo taskInfo = runningTaskInfo[0];
- if (shouldAddStubTaskView(runningTaskInfo)) {
+ boolean needGroupTaskView = runningTasks.length > 1;
+ if (shouldAddStubTaskView(runningTasks)) {
boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView;
if (needGroupTaskView) {
taskView = getTaskViewFromPool(true);
- RunningTaskInfo secondaryTaskInfo = runningTaskInfo[1];
- mTmpRunningTasks = new Task[]{
- Task.from(new TaskKey(taskInfo), taskInfo, false),
- Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false)
- };
+ mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
addView(taskView, 0);
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
@@ -2247,7 +2277,7 @@
addView(taskView, 0);
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
- mTmpRunningTasks = new Task[]{Task.from(new TaskKey(taskInfo), taskInfo, false)};
+ mTmpRunningTasks = new Task[]{runningTasks[0]};
taskView.bind(mTmpRunningTasks[0], mOrientationState);
}
runningTaskViewId = taskView.getTaskViewId();
@@ -2260,8 +2290,8 @@
measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
makeMeasureSpec(getMeasuredHeight(), EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
- } else if (getTaskViewByTaskId(taskInfo.taskId) != null) {
- runningTaskViewId = getTaskViewByTaskId(taskInfo.taskId).getTaskViewId();
+ } else if (getTaskViewByTaskId(runningTasks[0].key.id) != null) {
+ runningTaskViewId = getTaskViewByTaskId(runningTasks[0].key.id).getTaskViewId();
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -2336,10 +2366,9 @@
}
private void animateActionsViewIn() {
- ObjectAnimator anim = ObjectAnimator.ofFloat(
- mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
- anim.setDuration(TaskView.SCALE_ICON_DURATION);
- anim.start();
+ if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
+ animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION);
+ }
}
public void animateUpTaskIconScale() {
@@ -2676,13 +2705,6 @@
PendingAnimation anim) {
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
- runActionOnRemoteHandles(remoteTargetHandle -> {
- TransformParams params = remoteTargetHandle.getTransformParams();
- anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
- clampToProgress(FINAL_FRAME, 0, 0.5f));
- });
- }
anim.setFloat(taskView, VIEW_ALPHA, 0,
clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
@@ -2729,20 +2751,26 @@
mSplitHiddenTaskView.getThumbnail().getThumbnail(),
mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
- mTempRect, true /* fadeWithThumbnail */, true /* isStagedTask */);
+ mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+ true /* fadeWithThumbnail */, true /* isStagedTask */);
} else {
- mSplitSelectSource.view.setVisibility(INVISIBLE);
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
- mSplitSelectSource.view, null,
+ mSplitSelectSource.view, null /* thumbnail */,
mSplitSelectSource.drawable, startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
- mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
- mTempRect, true /* fadeWithThumbnail */, true /* isStagedTask */);
+ mFirstFloatingTaskView.addAnimation(anim, startingTaskRect, mTempRect,
+ false /* fadeWithThumbnail */, true /* isStagedTask */);
}
+ InteractionJankMonitorWrapper.begin(this,
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
anim.addEndListener(success -> {
if (success) {
mSplitToast.show();
+ InteractionJankMonitorWrapper.end(
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ } else {
+ InteractionJankMonitorWrapper.cancel(
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
}
});
}
@@ -3275,7 +3303,7 @@
// Update various scroll-dependent UI.
dispatchScrollChanged();
updateActionsViewFocusedScroll();
- if (isClearAllHidden()) {
+ if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
false);
}
@@ -3674,10 +3702,7 @@
// Update the pivots such that when the task is scaled, it fills the full page
getTaskSize(mTempRect);
- getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
- mActivity.getDeviceProfile(), mTempPointF);
- setPivotX(mTempPointF.x);
- setPivotY(mTempPointF.y);
+ updatePivots();
setTaskModalness(mTaskModalness);
mLastComputedTaskStartPushOutDistance = null;
mLastComputedTaskEndPushOutDistance = null;
@@ -3689,6 +3714,18 @@
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
+ private void updatePivots() {
+ if (mOverviewSelectEnabled) {
+ setPivotX(mLastComputedTaskSize.centerX());
+ setPivotY(mLastComputedTaskSize.bottom);
+ } else {
+ getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
+ mActivity.getDeviceProfile(), mTempPointF);
+ setPivotX(mTempPointF.x);
+ setPivotY(mTempPointF.y);
+ }
+ }
+
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
@@ -3991,21 +4028,32 @@
/**
* Confirms the selection of the next split task. The extra data is passed through because the
* user may be selecting a subtask in a group.
+ *
+ * @return true if waiting for confirmation of second app or if split animations are running,
+ * false otherwise
*/
- public void confirmSplitSelect(TaskView containerTaskView, Task task, IconView iconView,
+ public boolean confirmSplitSelect(TaskView containerTaskView, Task task, IconView iconView,
TaskThumbnailView thumbnailView) {
+ if (canLaunchFullscreenTask()) {
+ return false;
+ }
+ if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+ return true;
+ }
mSplitToast.cancel();
if (!task.isDockable) {
// Task not split screen supported
mSplitUnsupportedToast.show();
- return;
+ return true;
}
+ mSplitSelectStateController.setSecondTask(task);
RectF secondTaskStartingBounds = new RectF();
Rect secondTaskEndingBounds = new Rect();
// TODO(194414938) starting bounds seem slightly off, investigate
Rect firstTaskStartingBounds = new Rect();
Rect firstTaskEndingBounds = mTempRect;
- int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(mActivity,
+ false /* isToState */);
PendingAnimation pendingAnimation = new PendingAnimation(duration);
int halfDividerSize = getResources()
@@ -4026,9 +4074,11 @@
mSecondFloatingTaskView.setAlpha(1);
mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
- pendingAnimation.addEndListener(aBoolean ->
- mSplitSelectStateController.setSecondTask(
- task, aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
+ pendingAnimation.addEndListener(aBoolean -> {
+ mSplitSelectStateController.launchSplitTasks(
+ aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
+ InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+ });
if (containerTaskView.containsMultipleTasks()) {
// If we are launching from a child task, then only hide the thumbnail itself
mSecondSplitHiddenView = thumbnailView;
@@ -4036,10 +4086,14 @@
mSecondSplitHiddenView = containerTaskView;
}
mSecondSplitHiddenView.setVisibility(INVISIBLE);
+ InteractionJankMonitorWrapper.begin(this,
+ InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
pendingAnimation.buildAnim().start();
+ return true;
}
/** TODO(b/181707736) More gracefully handle exiting split selection state */
+ @SuppressLint("WrongCall")
protected void resetFromSplitSelectionState() {
if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
if (mFirstFloatingTaskView != null) {
@@ -4414,7 +4468,8 @@
}
RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
- mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
+ mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(
+ getContext(), recentsAnimationTargets);
mSplitBoundsConfig = gluer.getStagedSplitBounds();
// Add release check to the targets from the RemoteTargetGluer and not the targets
// passed in because in the event we're in split screen, we use the passed in targets
@@ -4469,10 +4524,7 @@
// Reset the minimized state since we force-toggled the minimized state when entering
// overview, but never actually finished the recents animation. This is a catch all for
// cases where we haven't already reset it.
- SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
- if (p != null) {
- p.setSplitScreenMinimized(false);
- }
+ SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
}
if (mRecentsAnimationController == null) {
@@ -4488,6 +4540,17 @@
final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
systemUiProxy.notifySwipeToHomeFinished();
systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx);
+ // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
+ // See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}.
+ PictureInPictureSurfaceTransaction tx =
+ new PictureInPictureSurfaceTransaction.Builder()
+ .setAlpha(0f)
+ .build();
+ int[] taskIds = TopTaskTracker.INSTANCE.get(getContext()).getRunningSplitTaskIds();
+ for (int taskId : taskIds) {
+ mRecentsAnimationController.setFinishTaskTransaction(taskId,
+ tx, null /* overlay */);
+ }
}
mRecentsAnimationController.finish(toRecents, () -> {
if (onFinishComplete != null) {
@@ -4525,6 +4588,7 @@
/**
* Updates page scroll synchronously after measure and layout child views.
*/
+ @SuppressLint("WrongCall")
public void updateScrollSynchronously() {
// onMeasure is needed to update child's measured width which is used in scroll calculation,
// in case TaskView sizes has changed when being focused/unfocused.
@@ -4825,6 +4889,17 @@
}
/**
+ * Update whether RecentsView is in select mode. Should be enabled before transitioning to
+ * select mode, and only disabled after transitioning from select mode.
+ */
+ public void setOverviewSelectEnabled(boolean overviewSelectEnabled) {
+ if (mOverviewSelectEnabled != overviewSelectEnabled) {
+ mOverviewSelectEnabled = overviewSelectEnabled;
+ updatePivots();
+ }
+ }
+
+ /**
* Switch the current running task view to static snapshot mode,
* capturing the snapshot at the same time.
*/
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index bff8651..d8120ff 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -283,6 +283,7 @@
public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
mFullscreenParams = fullscreenParams;
+ getTaskOverlay().setFullscreenParams(fullscreenParams);
invalidate();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ce033e5..b5971f2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -20,7 +20,6 @@
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -628,10 +627,7 @@
// Reset the minimized state since we force-toggled the minimized state when entering
// overview, but never actually finished the recents animation
- SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
- if (p != null) {
- p.setSplitScreenMinimized(false);
- }
+ SystemUiProxy.INSTANCE.get(getContext()).setSplitScreenMinimized(false);
mIsClickableAsLiveTile = false;
RemoteAnimationTargets targets;
@@ -698,14 +694,10 @@
* second app. {@code false} otherwise
*/
private boolean confirmSecondSplitSelectApp() {
- boolean isSelectingSecondSplitApp = getRecentsView().isSplitSelectionActive();
- if (isSelectingSecondSplitApp) {
- int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
- TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
- getRecentsView().confirmSplitSelect(this, container.getTask(), container.getIconView(),
- container.getThumbnailView());
- }
- return isSelectingSecondSplitApp;
+ int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
+ TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
+ return getRecentsView().confirmSplitSelect(this, container.getTask(),
+ container.getIconView(), container.getThumbnailView());
}
/**
@@ -855,7 +847,7 @@
}
private boolean showTaskMenu(IconView iconView) {
- if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
+ if (!getRecentsView().canLaunchFullscreenTask()) {
// Don't show menu when selecting second split screen app
return true;
}
@@ -1030,7 +1022,7 @@
}
public float getTaskCornerRadius() {
- return TaskCornerRadius.get(mActivity);
+ return mCurrentFullscreenParams.mCornerRadius;
}
@Override
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
new file mode 100644
index 0000000..929bff3
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/RecentsHitboxExtenderTest.java
@@ -0,0 +1,125 @@
+package com.android.launcher3.taskbar;
+
+import static android.view.MotionEvent.ACTION_DOWN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.DeviceProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+public class RecentsHitboxExtenderTest {
+
+ private static final int TASKBAR_OFFSET_Y = 35;
+ private static final int BUTTON_WIDTH = 10;
+ private static final int BUTTON_HEIGHT = 10;
+
+ private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
+ @Mock
+ View mMockRecentsButton;
+ @Mock
+ View mMockRecentsParent;
+ @Mock
+ DeviceProfile mMockDeviceProfile;
+ @Mock
+ Handler mMockHandler;
+ Context mContext;
+
+ float[] mRecentsCoords = new float[]{0,0};
+ private final Supplier<float[]> mSupplier = () -> mRecentsCoords;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getContext();
+ mHitboxExtender.init(mMockRecentsButton, mMockRecentsParent, mMockDeviceProfile, mSupplier,
+ mMockHandler);
+ when(mMockDeviceProfile.getTaskbarOffsetY()).thenReturn(TASKBAR_OFFSET_Y);
+ when(mMockRecentsButton.getContext()).thenReturn(mContext);
+ when(mMockRecentsButton.getWidth()).thenReturn(BUTTON_WIDTH);
+ when(mMockRecentsButton.getHeight()).thenReturn(BUTTON_HEIGHT);
+ }
+
+ @Test
+ public void noRecentsButtonClick_notActive() {
+ mHitboxExtender.onAnimationProgressToOverview(0);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertFalse(mHitboxExtender.extendedHitboxEnabled());
+ }
+
+ @Test
+ public void recentsButtonClick_active() {
+ mHitboxExtender.onRecentsButtonClicked();
+ mHitboxExtender.onAnimationProgressToOverview(0);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertTrue(mHitboxExtender.extendedHitboxEnabled());
+ }
+
+ @Test
+ public void homeToTaskbar_notActive() {
+ mHitboxExtender.onAnimationProgressToOverview(1);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertFalse(mHitboxExtender.extendedHitboxEnabled());
+ }
+
+ @Test
+ public void animationEndReset() {
+ mHitboxExtender.onRecentsButtonClicked();
+ mHitboxExtender.onAnimationProgressToOverview(0);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertTrue(mHitboxExtender.extendedHitboxEnabled());
+ mHitboxExtender.onAnimationProgressToOverview(1);
+ verify(mMockHandler, times(1)).postDelayed(any(), anyLong());
+ }
+
+ @Test
+ public void motionWithinHitbox() {
+ mHitboxExtender.onRecentsButtonClicked();
+ mHitboxExtender.onAnimationProgressToOverview(0);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertTrue(mHitboxExtender.extendedHitboxEnabled());
+ // Center width, past height but w/in offset bounds
+ MotionEvent motionEvent = getMotionEvent(ACTION_DOWN,
+ BUTTON_WIDTH / 2, BUTTON_HEIGHT + TASKBAR_OFFSET_Y / 2);
+ assertTrue(mHitboxExtender.onControllerInterceptTouchEvent(motionEvent));
+ }
+
+ @Test
+ public void motionOutsideHitbox() {
+ mHitboxExtender.onRecentsButtonClicked();
+ mHitboxExtender.onAnimationProgressToOverview(0);
+ mHitboxExtender.onAnimationProgressToOverview(0.5f);
+ assertTrue(mHitboxExtender.extendedHitboxEnabled());
+ // Center width, past height and offset
+ MotionEvent motionEvent = getMotionEvent(ACTION_DOWN,
+ BUTTON_WIDTH / 2, BUTTON_HEIGHT + TASKBAR_OFFSET_Y * 2);
+ assertFalse(mHitboxExtender.onControllerInterceptTouchEvent(motionEvent));
+ }
+
+ private MotionEvent getMotionEvent(int action, int x, int y) {
+ return MotionEvent.obtain(0, 0, action, x, y, 0);
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 3bd3722..5c2e14f 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -46,7 +46,8 @@
runWithShellPermission(() ->
usageStatsManager.registerAppUsageLimitObserver(observerId, packages,
Duration.ofSeconds(600), Duration.ofSeconds(300),
- PendingIntent.getActivity(mTargetContext, -1, new Intent(), 0)));
+ PendingIntent.getActivity(mTargetContext, -1, new Intent(),
+ PendingIntent.FLAG_MUTABLE)));
mLauncher.goHome();
final DigitalWellBeingToast toast = getToast();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index ca6712f..f7600ff 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -57,6 +57,7 @@
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
+import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.quickstep.views.RecentsView;
@@ -111,7 +112,8 @@
}
mOrderSensitiveRules = RuleChain
- .outerRule(new NavigationModeSwitchRule(mLauncher))
+ .outerRule(new SamplerRule())
+ .around(new NavigationModeSwitchRule(mLauncher))
.around(new FailureWatcher(mDevice, mLauncher));
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 4d38822..4bf247c 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -52,6 +52,8 @@
public class TaplTestsQuickstep extends AbstractQuickStepTest {
private static final String APP_NAME = "LauncherTestApp";
+ private static final String CALCULATOR_APP_PACKAGE =
+ resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
@Before
public void setUp() throws Exception {
@@ -73,7 +75,7 @@
public static void startTestApps() throws Exception {
startAppFast(getAppPackageName());
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startAppFast(CALCULATOR_APP_PACKAGE);
startTestActivity(2);
}
@@ -207,7 +209,7 @@
@NavigationModeSwitch
@PortraitLandscape
public void testBackground() throws Exception {
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startAppFast(CALCULATOR_APP_PACKAGE);
final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
assertNotNull("Background.switchToOverview() returned null",
@@ -302,7 +304,7 @@
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startAppFast(CALCULATOR_APP_PACKAGE);
mLauncher.pressBack();
mLauncher.getWorkspace();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml
new file mode 100644
index 0000000..94ef06c
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_close_enter.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_decelerate"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="-25%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml
new file mode 100644
index 0000000..19eb09e
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_close_exit.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml
new file mode 100644
index 0000000..f699cec
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_open_enter.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_decelerate"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="25%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml
new file mode 100644
index 0000000..85988ec
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_open_exit.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="-25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/color-night-v31/all_apps_button_color_2.xml b/res/color-night-v31/all_apps_button_color_2.xml
index 30b972f..e400543 100644
--- a/res/color-night-v31/all_apps_button_color_2.xml
+++ b/res/color-night-v31/all_apps_button_color_2.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent2_50"
+ android:color="@android:color/system_accent2_500"
android:lStar="98" />
</selector>
diff --git a/res/color-night-v31/folder_background_dark.xml b/res/color-night-v31/folder_background_dark.xml
index d607395..696e8ea 100644
--- a/res/color-night-v31/folder_background_dark.xml
+++ b/res/color-night-v31/folder_background_dark.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_neutral2_50"
+ android:color="@android:color/system_neutral2_500"
android:lStar="35" />
</selector>
diff --git a/res/color-night-v31/folder_preview_dark.xml b/res/color-night-v31/folder_preview_dark.xml
index a5bd636..bdd48a2 100644
--- a/res/color-night-v31/folder_preview_dark.xml
+++ b/res/color-night-v31/folder_preview_dark.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_neutral2_50"
+ android:color="@android:color/system_neutral2_500"
android:lStar="30" />
</selector>
diff --git a/res/color-v31/all_apps_button_bg_color.xml b/res/color-v31/all_apps_button_bg_color.xml
index 3ad38bc..8959020 100644
--- a/res/color-v31/all_apps_button_bg_color.xml
+++ b/res/color-v31/all_apps_button_bg_color.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_neutral1_50"
+ android:color="@android:color/system_neutral1_500"
android:lStar="98" />
</selector>
diff --git a/res/color-v31/all_apps_button_color_1.xml b/res/color-v31/all_apps_button_color_1.xml
index 2d0895e..71c7d8d 100644
--- a/res/color-v31/all_apps_button_color_1.xml
+++ b/res/color-v31/all_apps_button_color_1.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent1_50"
+ android:color="@android:color/system_accent1_500"
android:lStar="40" />
</selector>
diff --git a/res/color-v31/all_apps_button_color_2.xml b/res/color-v31/all_apps_button_color_2.xml
index 7674b43..608c8a9 100644
--- a/res/color-v31/all_apps_button_color_2.xml
+++ b/res/color-v31/all_apps_button_color_2.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent2_50"
+ android:color="@android:color/system_accent2_500"
android:lStar="48" />
</selector>
diff --git a/res/color-v31/all_apps_button_color_3.xml b/res/color-v31/all_apps_button_color_3.xml
index 17cb54f..dbb97b1 100644
--- a/res/color-v31/all_apps_button_color_3.xml
+++ b/res/color-v31/all_apps_button_color_3.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent1_50"
+ android:color="@android:color/system_accent1_500"
android:lStar="35" />
</selector>
diff --git a/res/color-v31/all_apps_button_color_4.xml b/res/color-v31/all_apps_button_color_4.xml
index a6150f1..d02528f6 100644
--- a/res/color-v31/all_apps_button_color_4.xml
+++ b/res/color-v31/all_apps_button_color_4.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent3_50"
+ android:color="@android:color/system_accent3_500"
android:lStar="48" />
</selector>
diff --git a/res/color-v31/folder_background_light.xml b/res/color-v31/folder_background_light.xml
index e3c7e7d..eb2fdd7 100644
--- a/res/color-v31/folder_background_light.xml
+++ b/res/color-v31/folder_background_light.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_neutral1_50"
+ android:color="@android:color/system_neutral1_500"
android:lStar="98" />
</selector>
diff --git a/res/color-v31/folder_preview_light.xml b/res/color-v31/folder_preview_light.xml
index fe30c87..ed1205e 100644
--- a/res/color-v31/folder_preview_light.xml
+++ b/res/color-v31/folder_preview_light.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:color="@android:color/system_accent2_50"
+ android:color="@android:color/system_accent2_500"
android:lStar="80" />
</selector>
diff --git a/res/drawable/gm_edit_24.xml b/res/drawable/gm_edit_24.xml
index 59a0dc2..f741333 100644
--- a/res/drawable/gm_edit_24.xml
+++ b/res/drawable/gm_edit_24.xml
@@ -5,6 +5,6 @@
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
- android:fillColor="@android:color/white"
+ android:fillColor="?android:attr/textColorPrimaryInverse"
android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
</vector>
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index b9942c0..81e94f7 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -18,10 +18,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurfaceVariant" />
<corners android:radius="@dimen/rounded_button_radius" />
<stroke
android:width="1dp"
- android:color="?androidprv:attr/colorAccentPrimaryVariant" />
+ android:color="?androidprv:attr/colorSurfaceVariant" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
diff --git a/res/interpolator/back_cancel.xml b/res/interpolator/back_cancel.xml
new file mode 100644
index 0000000..2165457
--- /dev/null
+++ b/res/interpolator/back_cancel.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, 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.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.2"
+ android:controlY1="0"
+ android:controlX2="0"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/interpolator/fast_out_extra_slow_in.xml b/res/interpolator/fast_out_extra_slow_in.xml
new file mode 100644
index 0000000..f296a82
--- /dev/null
+++ b/res/interpolator/fast_out_extra_slow_in.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
\ No newline at end of file
diff --git a/res/interpolator/standard_accelerate.xml b/res/interpolator/standard_accelerate.xml
new file mode 100644
index 0000000..394393d
--- /dev/null
+++ b/res/interpolator/standard_accelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.3"
+ android:controlY1="0"
+ android:controlX2="1"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/interpolator/standard_decelerate.xml b/res/interpolator/standard_decelerate.xml
new file mode 100644
index 0000000..579f4f5
--- /dev/null
+++ b/res/interpolator/standard_decelerate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0"
+ android:controlY1="0"
+ android:controlX2="0"
+ android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 0f6fc6c..feebfe1 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -22,7 +22,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
- android:importantForAccessibility="no"
+ android:importantForAccessibility="noHideDescendants"
android:layout_marginVertical="8dp">
<!-- The image of the widget. This view does not support padding. Any placement adjustment
should be done using margins. Width & height are set at runtime after scaling the
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index e867405..e3f1fca 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -38,12 +38,13 @@
android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"/>
<TextView
+ style="@style/PrimaryHeadline"
android:id="@+id/no_widgets_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
- android:textSize="20sp"
+ android:textSize="18sp"
android:layout_below="@id/search_and_recommendations_container"
tools:text="No widgets available" />
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 8f0eae7..3cdc2e8 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -21,7 +21,10 @@
android:layout_height="wrap_content"
android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
android:orientation="horizontal"
- launcher:appIconSize="48dp">
+ android:importantForAccessibility="yes"
+ android:focusable="true"
+ launcher:appIconSize="48dp"
+ android:descendantFocusability="afterDescendants">
<ImageView
android:id="@+id/app_icon"
@@ -32,14 +35,11 @@
tools:src="@drawable/ic_corp"/>
<LinearLayout
- android:id="@+id/app_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:orientation="vertical"
- android:focusable="true"
- android:descendantFocusability="afterDescendants">
+ android:orientation="vertical">
<TextView
android:id="@+id/app_title"
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index 73200de..f3b3053 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -16,9 +16,9 @@
<com.android.launcher3.allapps.WorkEduCard xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="@dimen/work_edu_card_margin"
+ android:layout_marginBottom="@dimen/work_edu_card_bottom_margin"
android:gravity="center">
-
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -36,30 +36,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/work_card_margin"
- android:layout_marginRight="@dimen/work_card_margin"
+ android:layout_marginEnd="@dimen/work_card_margin"
android:text="@string/work_profile_edu_work_apps"
+ android:textDirection="locale"
android:textSize="18sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/padded_rounded_button_height"
- android:orientation="horizontal"
- >
+ android:orientation="horizontal">
<FrameLayout
android:layout_width="@dimen/rounded_button_width"
android:layout_height="@dimen/rounded_button_width"
+ android:layout_alignParentEnd="true"
android:background="@drawable/rounded_action_button"
- android:padding="@dimen/rounded_button_padding"
- android:layout_alignParentRight="true">
+ android:padding="@dimen/rounded_button_padding">
<ImageButton
+ android:id="@+id/action_btn"
android:layout_width="@dimen/x_icon_size"
android:layout_height="@dimen/x_icon_size"
- android:id="@+id/action_btn"
- android:src="@drawable/ic_remove_no_shadow"
android:layout_gravity="center"
- android:padding="@dimen/x_icon_padding" />
+ android:padding="@dimen/x_icon_padding"
+ android:src="@drawable/ic_remove_no_shadow" />
</FrameLayout>
</RelativeLayout>
-
</RelativeLayout>
diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml
index c536d77..d2fa5fa 100644
--- a/res/layout/work_mode_fab.xml
+++ b/res/layout/work_mode_fab.xml
@@ -21,14 +21,14 @@
android:layout_width="wrap_content"
android:gravity="center"
android:includeFontPadding="false"
+ android:textDirection="locale"
android:drawableTint="@color/all_apps_tab_text"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp"
android:background="@drawable/work_apps_toggle_background"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_corp_off"
- android:layout_marginBottom="@dimen/work_fab_margin"
- android:layout_marginEnd="@dimen/work_fab_margin"
+ android:layout_marginBottom="@dimen/work_fab_margin_bottom"
android:paddingLeft="@dimen/work_mode_fab_padding"
android:paddingRight="@dimen/work_mode_fab_padding"
android:text="@string/work_apps_pause_btn_text" />
\ No newline at end of file
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 4c63af2..019fc5b 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeer tans; <xliff:g id="PROGRESS">%2$s</xliff:g> voltooi"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laai tans af, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wag tans om te installeer"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Programopdatering word vereis"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Die program vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Dateer op"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Verwyder"</string>
<string name="widgets_list" msgid="796804551140113767">"Legstukkelys"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Legstukkelys is toegemaak"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Voeg by tuisskerm"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e59ad1b..7de1041 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> በመጫን ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቅቋል"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> በመውረድ ላይ፣ <xliff:g id="PROGRESS">%2$s</xliff:g> ተጠናቋል"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ለመጫን በመጠበቅ ላይ"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"መተግበሪያ ማዘመን አስፈላጊ ነው"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"የዚህ አዶ መተግበሪያ አልተዘመነም። ይህን አቋራጭ ዳግም ለማንቃት በራስዎ ማዘመን ወይም አዶውን ማስወገድ ይችላሉ።"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"አዘምን"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"አስወግድ"</string>
<string name="widgets_list" msgid="796804551140113767">"የመግብሮች ዝርዝር"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"የመግብሮች ዝርዝር ተዘግቷል"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ወደ መነሻ ማያ ገጽ አክል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 681fa2f..bcd1fa9 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"تقسيم لليسار"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"تقسيم لليمين"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"معلومات تطبيق %1$s"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة"</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"العرض %1$d الطول %2$d"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"جارٍ تثبيت <xliff:g id="NAME">%1$s</xliff:g>، مستوى التقدم: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"جارٍ تنزيل <xliff:g id="NAME">%1$s</xliff:g>، اكتمل <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> في انتظار التثبيت"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"مطلوب تحديث التطبيق"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"تحديث"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"إزالة"</string>
<string name="widgets_list" msgid="796804551140113767">"قائمة الأدوات"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة الأدوات."</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"إضافة تطبيق للشاشة الرئيسية"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 4c21093..e3f637f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হৈছে"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনল’ড কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হ’ল"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল হোৱালৈ অপেক্ষা কৰি থকা হৈছে"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"এপ্টো আপডে’ট কৰা প্ৰয়োজন"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"এই চিহ্নটোৰ এপ্টো আপডে’ট কৰা হোৱা নাই। আপুনি এই শ্বৰ্টকাটটো পুনৰ সক্ষম কৰিবলৈ মেনুৱেলী আপডে’ট কৰিব পাৰে অথবা চিহ্নটো আঁতৰাব পাৰে।"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"আপডে’ট কৰক"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"আঁতৰাওক"</string>
<string name="widgets_list" msgid="796804551140113767">"ৱিজেটৰ তালিকা"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ৱিজেটৰ তালিকা বন্ধ কৰা হ’ল"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"গৃহ স্ক্ৰীনত যোগ কৰক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index ec644a1..47fde2a 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> quraşdırır, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlanıb"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> endirilir, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> yüklənmək üçün gözləyir"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Tətbiqin güncəllənməsi tələb edilir"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Bu ikona üçün tətbiq güncəllənməyib. Bu qısayolu yenidən aktivləşdirmək üçün manual olaraq güncəlləyə və ya ikonanı silə bilərsiniz."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Güncəlləyin"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Silin"</string>
<string name="widgets_list" msgid="796804551140113767">"Vidcet siyahısı"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Vidcet siyahısı bağlandı"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Əsas ekrana əlavə edin"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 38c2667..6ad0ea9 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -47,7 +47,7 @@
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Pretražite"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Obrišite tekst iz okvira za pretragu"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"Vidžeti i prečice nisu dostupni"</string>
- <string name="no_search_results" msgid="3787956167293097509">"Nije pronađen nijedan vidžet ili prečica"</string>
+ <string name="no_search_results" msgid="3787956167293097509">"Nije pronađen nijedan vidžet ni prečica"</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Lično"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Konverzacije"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se instalira, <xliff:g id="PROGRESS">%2$s</xliff:g> gotovo"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka na instaliranje"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Treba da ažurirate aplikaciju"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za ovu ikonu nije ažurirana. Možete da je ručno ažurirate da biste ponovo omogućili ovu prečicu ili uklonite ikonu."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Ukloni"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista vidžeta"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista vidžeta je zatvorena"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Dodajte na početni ekran"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index cc0c40d..3617fcc 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Усталёўваецца праграма \"<xliff:g id="NAME">%1$s</xliff:g>\", завершана <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Ідзе спампоўка <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завершана"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чакае ўсталёўкі"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Неабходна абнавіць праграму"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Гэта версія праграмы састарэла. Абнавіце праграму ўручную, каб зноў карыстацца гэтым ярлыком, або выдаліце значок."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Абнавіць"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Выдаліць"</string>
<string name="widgets_list" msgid="796804551140113767">"Спіс віджэтаў"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Спіс віджэтаў закрыты"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Дадаць на галоўны экран"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 5601c26..1a8671a 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> завършено"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се изтегля. Завършено: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> изчаква инсталиране"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Изисква се актуализация на приложението"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Приложението за тази икона не е актуализирано. Можете да го актуализирате ръчно, за да активирате отново този пряк път, или да премахнете иконата."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Актуализиране"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Премахване"</string>
<string name="widgets_list" msgid="796804551140113767">"Списък с приспособления"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Списъкът с приспособления е затворен"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Добавяне към началния екран"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 8b4cd72..7b45842 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টল করা হচ্ছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূর্ণ হয়েছে"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনলোড হচ্ছে <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পন্ন হয়েছে"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনস্টলের অপেক্ষায় রয়েছে"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"অ্যাপটি আপডেট করা প্রয়োজন"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"এই আইকনের জন্য অ্যাপটি আপডেট করা নেই। এই শর্টকার্ট আবার চালু করতে, আপনি ম্যানুয়ালি আপডেট করতে বা সরিয়ে দিতে পারবেন।"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"আপডেট করুন"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"সরান"</string>
<string name="widgets_list" msgid="796804551140113767">"উইজেটের তালিকা"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"উইজেটের তালিকা বন্ধ করা হয়েছে"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"হোম স্ক্রিনে যোগ করুন"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index cf3163b..fbe4bf1 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, završeno je <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se preuzima, završeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> čeka da se instalira"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Potrebno je ažurirati aplikaciju"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za ovu ikonu nije ažurirana. Možete je ažurirati ručno da ponovo omogućite ovu prečicu ili možete ukloniti ikonu."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Ukloni"</string>
<string name="widgets_list" msgid="796804551140113767">"Spisak vidžeta"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Spisak vidžeta je zatvoren"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Dodavanje na početni ekran"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 1f3315a..5d58a90 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"S\'està instal·lant <xliff:g id="NAME">%1$s</xliff:g>; s\'ha completat un <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"S\'està baixant <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completat"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"S\'està esperant per instal·lar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Cal actualitzar l\'aplicació"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'aplicació d\'aquesta icona no està actualitzada. Pots actualitzar-la manualment per tornar a activar aquesta drecera o pots suprimir la icona."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Actualitza"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Suprimeix"</string>
<string name="widgets_list" msgid="796804551140113767">"Llista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"S\'ha tancat la llista de widgets"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Afegeix a la pantalla d\'inici"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 225d7ee..2b0b100 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"Rozdělit vlevo"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"Rozdělit vpravo"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Klepnutím a podržením přesunete widget."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Widget přesunete klepnutím a podržením."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvojitým klepnutím a podržením přesunete widget, případně použijte vlastní akce."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g>, dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Stahování aplikace <xliff:g id="NAME">%1$s</xliff:g> (dokončeno <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalace aplikace <xliff:g id="NAME">%1$s</xliff:g> čeká na zahájení"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Je nutná aktualizace aplikace"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikace pro tuto ikonu není nainstalována. Můžete ji ručně aktualizovat, aby zkratka znovu fungovala, případně můžete ikonu odstranit."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Aktualizovat"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Odstranit"</string>
<string name="widgets_list" msgid="796804551140113767">"Seznam widgetů"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Seznam widgetů zavřen"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Přidat na plochu"</string>
@@ -156,7 +160,7 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobní"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovní"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovní profil"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovní aplikace jsou označené a viditelné vašemu administrátorovi IT"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovní aplikace jsou označené a váš administrátor IT je vidí"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"Rozumím"</string>
<string name="work_apps_paused_title" msgid="3040901117349444598">"Pracovní aplikace jsou pozastaveny"</string>
<string name="work_apps_paused_body" msgid="261634750995824906">"Pracovní aplikace vám nemohou zasílat oznámení, používat vaši baterii ani získat přístup k vaší poloze"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 094d84e..45d8358 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeres. <xliff:g id="PROGRESS">%2$s</xliff:g> fuldført"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloades. <xliff:g id="PROGRESS">%2$s</xliff:g> er gennemført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> venter på at installere"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Appen skal opdateres"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Appen, der tilhører dette ikon, er ikke opdateret. Du kan opdatere appen manuelt for at genaktivere denne genvej, eller du kan fjerne ikonet."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Opdater"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Fjern"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste med widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Listen med widgets blev lukket"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Føj til startskærm"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 4b92155..140584c 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> wird installiert, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wird heruntergeladen, <xliff:g id="PROGRESS">%2$s</xliff:g> abgeschlossen"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Warten auf Installation von <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App-Update erforderlich"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Die App für dieses Symbol wurde noch nicht aktualisiert. Du kannst sie manuell aktualisieren, um die Verknüpfung wieder zu aktivieren, oder das Symbol entfernen."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Aktualisieren"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Entfernen"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgetliste"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgetliste geschlossen"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Zum Startbildschirm hinzufügen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 667d511..5bbc008 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Έχει ολοκληρωθεί το <xliff:g id="PROGRESS">%2$s</xliff:g> της εγκατάστασης της εφαρμογής <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Λήψη <xliff:g id="NAME">%1$s</xliff:g>, ολοκληρώθηκε <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> σε αναμονή για εγκατάσταση"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Απαιτείται ενημέρωση της εφαρμογής"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Η εφαρμογή για αυτό το εικονίδιο δεν έχει ενημερωθεί. Μπορείτε να την ενημερώσετε μη αυτόματα για να ενεργοποιήσετε ξανά τη συγκεκριμένη συντόμευση ή να καταργήσετε το εικονίδιο."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ενημέρωση"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Κατάργηση"</string>
<string name="widgets_list" msgid="796804551140113767">"Λίστα γραφικών στοιχείων"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Η λίστα γραφικών στοιχείων έκλεισε"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Προσθήκη στην αρχική οθόνη"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 455aea6..62fa92d 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -102,7 +102,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & style"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Allow home screen rotation"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remove"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 455aea6..62fa92d 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -102,7 +102,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & style"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Allow home screen rotation"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remove"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 455aea6..62fa92d 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -102,7 +102,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & style"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Allow home screen rotation"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remove"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 455aea6..62fa92d 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -102,7 +102,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper & style"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Allow home screen rotation"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut or remove the icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remove"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 4a4dc49..90cd6cd 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installing, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App update required"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remove"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index fad6690..1737658 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Se está instalando <xliff:g id="NAME">%1$s</xliff:g>; <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Se completó el <xliff:g id="PROGRESS">%2$s</xliff:g> de la descarga de <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Instalación de <xliff:g id="NAME">%1$s</xliff:g> en espera"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Es necesario actualizar la app"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"No se actualizó la app de este ícono. Puedes actualizarla manualmente para rehabilitar el acceso directo, o bien quitar el ícono."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Quitar"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Se cerró la lista de widgets"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Agregar a pantalla principal"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index ef7271e..21b81cb 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -23,8 +23,8 @@
<string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
<string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo seguro"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"Aplicación descargada inhabilitada en modo Seguro"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"Dividir parte izquierda"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"Dividir parte derecha"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Mantén pulsado un widget para moverlo"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toca dos veces y mantén pulsado un widget para moverlo o usar acciones personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de ancho por %2$d de alto"</string>
@@ -41,7 +41,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"Añadir a pantalla de inicio"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> añadido a la pantalla de inicio"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
- <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# combinación de teclas}other{# combinaciones de teclas}}"</string>
+ <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# acceso directo}other{# accesos directos}}"</string>
<string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Buscar"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Debes actualizar la aplicación"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"La aplicación de este icono no está actualizada. Puedes actualizarla manualmente para volver a habilitar este acceso directo o puedes eliminar el icono."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Quitar"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets cerrada"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Añadir a pantalla de inicio"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 8f6a8d5..7ded0de 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Üksust <xliff:g id="NAME">%1$s</xliff:g> installitakse, <xliff:g id="PROGRESS">%2$s</xliff:g> on valmis"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Rakenduse <xliff:g id="NAME">%1$s</xliff:g> allalaadimine, <xliff:g id="PROGRESS">%2$s</xliff:g> on valmis"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> on installimise ootel"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Rakendust tuleb värskendada"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Selle ikooni rakendust pole värskendatud. Otsetee uuesti lubamiseks võite rakendust käsitsi värskendada või ikooni eemaldada."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Värskenda"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Eemalda"</string>
<string name="widgets_list" msgid="796804551140113767">"Vidinate loend"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Vidinate loend on suletud"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Lisa avakuvale"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 995f8dd..48d26f0 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> instalatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> deskargatzen, <xliff:g id="PROGRESS">%2$s</xliff:g> osatuta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> instalatzeko zain"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Aplikazioa eguneratu behar da"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Ikonoaren aplikazioa ez dago eguneratuta. Lasterbidea berriro gaitzeko, eskuz egunera dezakezu aplikazioa. Bestela, kendu ikonoa."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Eguneratu"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Kendu"</string>
<string name="widgets_list" msgid="796804551140113767">"Widget-zerrenda"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Itxi da widget-zerrenda"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Gehitu hasierako pantailan"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index ee97518..e834d0c 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> درحال نصب است، <xliff:g id="PROGRESS">%2$s</xliff:g> تکمیل شده است"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"درحال بارگیری <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="PROGRESS">%2$s</xliff:g> کامل شد"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> درانتظار نصب"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"برنامه باید بهروز شود"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"برنامه برای این نماد بهروز نشده است. میتوانید آن را بهصورت دستی بهروز کنید تا میانبر دوباره فعال شود، یا نماد را بردارید."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"بهروزرسانی"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"برداشتن"</string>
<string name="widgets_list" msgid="796804551140113767">"فهرست ابزارکها"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"فهرست ابزارکها بسته شد"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"افزودن به صفحه اصلی"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 21006e6..26ad66e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> asennetaan, <xliff:g id="PROGRESS">%2$s</xliff:g> valmis"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> latautuu, valmiina <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> odottaa asennusta"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Sovelluspäivitys vaaditaan"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Kuvakkeen sovellusta ei ole päivitetty. Voit ottaa pikakuvakkeen uudelleen käyttöön päivittämällä sovelluksen tai poistaa kuvakkeen."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Päivitä"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Poista"</string>
<string name="widgets_list" msgid="796804551140113767">"Widget-luettelo"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widget-luettelo suljettu"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Lisää aloitusnäytölle"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 0c023a3..1fc14f5 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'application <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <xliff:g id="NAME">%1$s</xliff:g> : <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'application requise"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'application pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Mettre à jour"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Retirer"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste des widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Liste des widgets fermée"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Ajouter à l\'écran d\'accueil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 5199c89..5ef521e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -124,12 +124,16 @@
<string name="app_installing_title" msgid="5864044122733792085">"Installation de <xliff:g id="NAME">%1$s</xliff:g>… (<xliff:g id="PROGRESS">%2$s</xliff:g> terminés)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> en cours de téléchargement, <xliff:g id="PROGRESS">%2$s</xliff:g> effectué(s)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'appli requise"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'appli correspondant à cette icône n\'est pas mise à jour. Vous pouvez la mettre à jour manuellement pour réactiver le raccourci ou supprimer l\'icône."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Modifier"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Supprimer"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste des widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"La liste des widgets est fermée"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Ajouter à l\'écran d\'accueil"</string>
<string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"L\'élément a bien été ajouté à l\'écran d\'accueil."</string>
- <string name="item_removed" msgid="851119963877842327">"L\'élément a bien été supprimé."</string>
+ <string name="item_removed" msgid="851119963877842327">"Élément supprimé"</string>
<string name="undo" msgid="4151576204245173321">"Annuler"</string>
<string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"Déplacer vers la ligne <xliff:g id="NUMBER_0">%1$s</xliff:g>, colonne <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index eee0032..2879f0d 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> completado"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Descargando <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="PROGRESS">%2$s</xliff:g> completado)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Esperando para instalar <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"É necesario actualizar a aplicación"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"A aplicación á que corresponde esta icona non está actualizada. Podes actualizala manualmente para activar de novo este atallo, ou ben quitar a icona."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Actualizar"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Quitar"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Pechouse a lista de widgets"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Engadir á pantalla de inicio"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 39f61ba..ce02af6 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"વ્યક્તિગત ઍપની સૂચિ"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"કાર્યસ્થળની ઍપની સૂચિ"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"કાઢી નાખો"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"અનઇન્સ્ટોલ કરો"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"અનઇન્સ્ટૉલ કરો"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ઍપની માહિતી"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ઇન્સ્ટૉલ કરો"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ઍપ સૂચવશો નહીં"</string>
@@ -124,12 +124,16 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ઇન્સ્ટૉલ કરી રહ્યાં છીએ, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ થયું"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ડાઉનલોડ કરી રહ્યાં છે, <xliff:g id="PROGRESS">%2$s</xliff:g> પૂર્ણ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>, ઇન્સ્ટૉલ થવાની રાહ જોઈ રહ્યું છે"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ઍપને અપડેટ કરવી જરૂરી છે"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"આ આઇકન માટે ઍપ અપડેટ કરવામાં આવી નથી. તમે આ શૉર્ટકટ ફરી ચાલુ કરવા અથવા આઇકન કાઢી નાખવા માટે ઍપને મેન્યુઅલી અપડેટ કરી શકો છો."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"અપડેટ કરો"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"કાઢી નાખો"</string>
<string name="widgets_list" msgid="796804551140113767">"વિજેટની સૂચિ"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"વિજેટની સૂચિ બંધ કરવામાં આવી છે"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"હોમ સ્ક્રીનમાં ઉમેરો"</string>
<string name="action_move_here" msgid="2170188780612570250">"આઇટમ અહીં ખસેડો"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"હોમ સ્ક્રીનમાં આઇટમ ઉમેરી"</string>
- <string name="item_removed" msgid="851119963877842327">"આઇટમ દૂર કરી"</string>
+ <string name="item_removed" msgid="851119963877842327">"આઇટમ કાઢી નાખી"</string>
<string name="undo" msgid="4151576204245173321">"રદ કરો"</string>
<string name="action_move" msgid="4339390619886385032">"આઇટમ ખસેડો"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g> પંક્તિ <xliff:g id="NUMBER_1">%2$s</xliff:g> કૉલમ પર ખસેડો"</string>
@@ -156,12 +160,12 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"મનગમતી ઍપ"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"ઑફિસની ઍપ"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"ઑફિસની પ્રોફાઇલ"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT વ્યવસ્થાપક તેમને જોઈ શકે છે"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"સમજાઈ ગયું"</string>
- <string name="work_apps_paused_title" msgid="3040901117349444598">"ઑફિસ માટેની ઍપ થોભાવવામાં આવેલ છે"</string>
+ <string name="work_apps_paused_title" msgid="3040901117349444598">"ઑફિસ માટેની ઍપ થોભાવવામાં આવી છે"</string>
<string name="work_apps_paused_body" msgid="261634750995824906">"ઑફિસ માટેની તમારી ઍપ તમને નોટિફિકેશન મોકલી શકતી નથી, તમારી બૅટરી વાપરી શકતી નથી કે તમારું સ્થાન ઍક્સેસ કરી શકતી નથી"</string>
<string name="work_apps_paused_content_description" msgid="5149623040804051095">"ઑફિસ માટેની ઍપ બંધ છે. ઑફિસ માટેની તમારી ઍપ તમને નોટિફિકેશન મોકલી શકતી નથી, તમારી બૅટરી વાપરી શકતી નથી કે તમારું સ્થાન ઍક્સેસ કરી શકતી નથી"</string>
- <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT વ્યવસ્થાપક તેમને જોઈ શકે છે"</string>
+ <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે"</string>
<string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"સમજાઈ ગયું"</string>
<string name="work_apps_pause_btn_text" msgid="1921059713673767460">"ઑફિસ માટેની ઍપ બંધ કરો"</string>
<string name="work_apps_enable_btn_text" msgid="1156432622148413741">"ઑફિસ માટેની ઍપ ચાલુ કરો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 6125205..5ed7bc8 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -108,7 +108,7 @@
<string name="allow_rotation_title" msgid="7222049633713050106">"होम स्क्रीन घुमाने की अनुमति दें"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फ़ोन घुुमाए जाने पर"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"सूचनाएं बताने वाला डॉट"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"चालू"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"चालू है"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"चालू"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के ऐक्सेस की ज़रूरत है"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल किया जा रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरा हो गया"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड हो रहा है, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरी हुई"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> के इंस्टॉल होने की प्रतीक्षा की जा रही है"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ऐप्लिकेशन को अपडेट करना ज़रूरी है"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"इस आइकॉन का ऐप्लिकेशन अपडेट नहीं है. इस शॉर्टकट को फिर से चालू करने या आइकॉन को हटाने के लिए, ऐप्लिकेशन को मैन्युअल रूप से अपडेट किया जा सकता है."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"अपडेट करें"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"हटाएं"</string>
<string name="widgets_list" msgid="796804551140113767">"विजेट की सूची"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"विजेट की सूची बंद हो गई है"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रीन पर जोड़ें"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index f12af71..06a330f 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> dovršeno"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Preuzimanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>, dovršeno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Čekanje na instaliranje aplikacije <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Aplikacija se treba ažurirati"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija ove ikone nije ažurirana. Možete ručno ažurirati da biste ponovo omogućili ovaj prečac ili uklonite ikonu."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ažuriraj"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Ukloni"</string>
<string name="widgets_list" msgid="796804551140113767">"Popis widgeta"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Popis widgeta zatvoren"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Dodajte na početni zaslon"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index d48792a..2d67fc3 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Folyamatban van a(z) <xliff:g id="NAME">%1$s</xliff:g> telepítése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A(z) <xliff:g id="NAME">%1$s</xliff:g> letöltése, <xliff:g id="PROGRESS">%2$s</xliff:g> kész"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A(z) <xliff:g id="NAME">%1$s</xliff:g> telepítésre vár"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Alkalmazásfrissítés szükséges"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Az ikonhoz tartozó alkalmazás nincs frissítve. A parancsikon újbóli engedélyezéséhez frissítse az alkalmazást, vagy távolítsa ez az ikont."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Frissítés"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Eltávolítás"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgetlista"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgetlista bezárva"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Hozzáadás a kezdőképernyőhöz"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 1c90768..e76ed7e 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -78,11 +78,11 @@
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Թաքցնել առաջարկը"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Ամրացնել առաջարկվող հավելվածը"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"Դյուրանցումների տեղադրում"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Հավելվածին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"կարդալ հիմնական էկրանի կարգավորումներն ու դյուրանցումները"</string>
- <string name="permdesc_read_settings" msgid="4208061150510996676">"Ծրագրին թույլ է տալիս կարդալ հիմնական էկրանի կարգավորումներն ու դյուրանցումները։"</string>
+ <string name="permdesc_read_settings" msgid="4208061150510996676">"Հավելվածին թույլ է տալիս կարդալ հիմնական էկրանի կարգավորումներն ու դյուրանցումները։"</string>
<string name="permlab_write_settings" msgid="4820028712156303762">"փոփոխել հիմնական էկրանի կարգավորումներն ու դյուրանցումները"</string>
- <string name="permdesc_write_settings" msgid="726859348127868466">"Ծրագրին թույլ է տալիս փոփոխել հիմնական էկրանի կարգավորումներն ու դյուրանցումները։"</string>
+ <string name="permdesc_write_settings" msgid="726859348127868466">"Հավելվածին թույլ է տալիս փոփոխել հիմնական էկրանի կարգավորումներն ու դյուրանցումները։"</string>
<string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածին չի թույլատրվում հեռախոսազանգեր կատարել"</string>
<string name="gadget_error_text" msgid="740356548025791839">"Չհաջողվեց բեռնել վիջեթը"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"Վիջեթի կարգավորումներ"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> հավելվածը տեղադրվում է, կատարված է <xliff:g id="PROGRESS">%2$s</xliff:g>-ը"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>–ի ներբեռնում (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>-ի տեղադրման սպասում"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Պահանջվում է թարմացնել հավելվածը"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Հավելվածը հնացել է։ Թարմացրեք այն ձեռքով, որպեսզի շարունակեք օգտագործել դյուրանցումը, կամ հեռացրեք հավելվածի պատկերակը։"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Թարմացնել"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Հեռացնել"</string>
<string name="widgets_list" msgid="796804551140113767">"Վիջեթների ցանկ"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Վիջեթների ցանկը փակվեց"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Ավելացնել հիմնական էկրանին"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index ee64f2c..b213d47 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> sedang diinstal, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> sedang didownload, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu dipasang"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Aplikasi perlu diupdate"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikasi untuk ikon ini belum diupdate. Anda dapat mengupdate secara manual untuk mengaktifkan kembali pintasan ini, atau hapus ikon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Hapus"</string>
<string name="widgets_list" msgid="796804551140113767">"Daftar widget"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Daftar widget ditutup"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Tambahkan ke layar utama"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7f7b560..95b6c1e 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Setur upp <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> í niðurhali, <xliff:g id="PROGRESS">%2$s</xliff:g> lokið"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> bíður uppsetningar"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Uppfæra þarf forritið"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Forritið fyrir þetta tákn er ekki uppfært. Þú getur uppfært það handvirkt til að kveikja aftur á þessari flýtileið eða fjarlægt táknið."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Uppfæra"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Fjarlægja"</string>
<string name="widgets_list" msgid="796804551140113767">"Græjulisti"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Græjulista lokað"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Bæta á heimaskjá"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 55bc9ac..4664726 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -77,7 +77,7 @@
<string name="install_drop_target_label" msgid="2539096853673231757">"Installa"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Non suggerire app"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Blocca previsione"</string>
- <string name="permlab_install_shortcut" msgid="5632423390354674437">"aggiunta di scorciatoie"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"Aggiunta di scorciatoie"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Consente a un\'app di aggiungere scorciatoie automaticamente."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"leggere le impostazioni e le scorciatoie nella schermata Home"</string>
<string name="permdesc_read_settings" msgid="4208061150510996676">"Consente all\'app di leggere le impostazioni e le scorciatoie nella schermata Home."</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Installazione di <xliff:g id="NAME">%1$s</xliff:g>, completamento: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Download di <xliff:g id="NAME">%1$s</xliff:g> in corso, <xliff:g id="PROGRESS">%2$s</xliff:g> completato"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> in attesa di installazione"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"È necessario aggiornare l\'app"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'app relativa a questa icona non è aggiornata. Puoi eseguire manualmente l\'aggiornamento per riattivare questa scorciatoia oppure rimuovere l\'icona."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Aggiorna"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Rimuovi"</string>
<string name="widgets_list" msgid="796804551140113767">"Elenco di widget"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Elenco di widget chiuso"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Aggiungi alla schermata Home"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index e374ced..b2ecf62 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"פיצול שמאלה"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"פיצול ימינה"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"פרטים על האפליקציה %1$s"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"כדי להעביר ווידג\'ט למקום אחר יש לגעת ולא להרפות."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"להעברת ווידג\'ט למקום אחר לוחצים עליו לחיצה ארוכה."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"כדי להעביר ווידג\'ט למקום אחר או להשתמש בפעולות מותאמות אישית, יש ללחוץ פעמיים ולא להרפות."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"רוחב %1$d על גובה %2$d"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> בתהליך התקנה, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"הורדת <xliff:g id="NAME">%1$s</xliff:g> מתבצעת, <xliff:g id="PROGRESS">%2$s</xliff:g> הושלמו"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"מחכה להתקנה של <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"נדרש עדכון לאפליקציה"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"האפליקציה של הסמל הזה לא מעודכנת. אפשר לעדכן אותה ידנית כדי להפעיל מחדש את קיצור הדרך הזה, או להסיר את הסמל."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"עדכון"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"הסרה"</string>
<string name="widgets_list" msgid="796804551140113767">"רשימת ווידג\'טים"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"רשימת הווידג\'טים נסגרה"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"הוספה למסך הבית"</string>
@@ -153,7 +157,7 @@
<string name="action_dismiss_notification" msgid="5909461085055959187">"סגירה"</string>
<string name="accessibility_close" msgid="2277148124685870734">"סגירה"</string>
<string name="notification_dismissed" msgid="6002233469409822874">"ההתראה נסגרה"</string>
- <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישיות"</string>
+ <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישי"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
<string name="work_profile_edu_work_apps" msgid="7895468576497746520">"האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT"</string>
@@ -163,7 +167,7 @@
<string name="work_apps_paused_content_description" msgid="5149623040804051095">"האפליקציות לעבודה מושבתות. האפליקציות לא יכולות לשלוח לך התראות, להשתמש בסוללה או לגשת למיקום שלך"</string>
<string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT"</string>
<string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"הבנתי"</string>
- <string name="work_apps_pause_btn_text" msgid="1921059713673767460">"כיבוי של אפליקציות לעבודה"</string>
+ <string name="work_apps_pause_btn_text" msgid="1921059713673767460">"השבתה של אפליקציות לעבודה"</string>
<string name="work_apps_enable_btn_text" msgid="1156432622148413741">"הפעלה של אפליקציות לעבודה"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"סינון"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"הפעולה נכשלה: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 83c7f39..9e921dc 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> をインストールしています: <xliff:g id="PROGRESS">%2$s</xliff:g> 完了"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>をダウンロード中、<xliff:g id="PROGRESS">%2$s</xliff:g>完了"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>のインストール待ち"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"アプリの更新が必要"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"このアイコンのアプリは更新されていません。手動で更新して、このショートカットを再度有効にできます。また、アイコンを削除することもできます。"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"更新"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"削除"</string>
<string name="widgets_list" msgid="796804551140113767">"ウィジェット リスト"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ウィジェット リストを閉じました"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ホーム画面に追加"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 9208448..e3d762d 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"ინსტალირდება <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულებულია"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"მიმდინარეობს <xliff:g id="NAME">%1$s</xliff:g>-ის ჩამოტვირთვა, <xliff:g id="PROGRESS">%2$s</xliff:g> დასრულდა"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ელოდება ინსტალაციას"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"საჭიროა აპის განახლება"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ამ ხატულის აპი განახლებული არ არის. შეგიძლიათ, ხელით განაახლოთ ამ მალსახმობის ხელახლა გასააქტიურებლად, ან ამოშალოთ ხატულა."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"განახლება"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ამოშლა"</string>
<string name="widgets_list" msgid="796804551140113767">"ვიჯეტების სია"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ვიჯეტების სია დაიხურა"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"მთავარ ეკრანზე დამატება"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 1383216..d84d3d9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -51,7 +51,7 @@
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Жеке виджеттер"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жұмыс виджеттері"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Әңгімелер"</string>
- <string name="widget_education_header" msgid="4874760613775913787">"Саусақпен түртсеңіз болғаны – пайдалы ақпарат көз алдыңызда"</string>
+ <string name="widget_education_header" msgid="4874760613775913787">"Саусақпен түртсеңіз болғаны – пайдалы ақпарат көз алдыңызда"</string>
<string name="widget_education_content" msgid="1731667670753497052">"Қолданбаларды ашпай-ақ ақпарат алу үшін негізгі экранға тиісті виджеттерді қосыңыз."</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Виджет параметрлерін өзгерту үшін түртіңіз."</string>
<string name="widget_education_close_button" msgid="8676165703104836580">"Түсінікті"</string>
@@ -75,7 +75,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Жою"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Қолданба ақпараты"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Орнату"</string>
- <string name="dismiss_prediction_label" msgid="3357562989568808658">"Қолданбаны ұсынбау"</string>
+ <string name="dismiss_prediction_label" msgid="3357562989568808658">"Қолданба ұсынбау"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Болжанған қолданбаны бекіту"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"таңбаша орнату"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді."</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> орнатылуда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктелуде, <xliff:g id="PROGRESS">%2$s</xliff:g> аяқталды"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнату күтілуде"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Қолданбаны жаңарту қажет"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Осы белгіше үшін қолданба жаңартылмаған. Оны қолмен жаңартып, осы таңбашаны қайта іске қоса аласыз немесе белгішені өшіріп тастаңыз."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Жаңарту"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Өшіру"</string>
<string name="widgets_list" msgid="796804551140113767">"Виджеттер тізімі"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Видджеттер тізімі жабылды"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Негізгі экранға қосу"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 2404995..7545216 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"បញ្ជីកម្មវិធីផ្ទាល់ខ្លួន"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"បញ្ជីកម្មវិធីការងារ"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"យកចេញ"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"លុបការដំឡើង"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"លុប"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ព័ត៌មានកម្មវិធី"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ដំឡើង"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"កុំណែនាំកម្មវិធី"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"កំពុងដំឡើង <xliff:g id="NAME">%1$s</xliff:g>, បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"កំពុងដោនឡូត <xliff:g id="NAME">%1$s</xliff:g> បានបញ្ចប់ <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> កំពុងរង់ចាំការដំឡើង"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"តម្រូវឱ្យមានកំណែកម្មវិធីថ្មី"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"កម្មវិធីសម្រាប់រូបតំណាងនេះមិនត្រូវបានដំឡើងកំណែទេ។ អ្នកអាចដំឡើងកំណែដោយផ្ទាល់ ដើម្បីបើកផ្លូវកាត់នេះឡើងវិញ ឬលុបរូបតំណាងនេះ។"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"ដំឡើងកំណែ"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"លុប"</string>
<string name="widgets_list" msgid="796804551140113767">"បញ្ជីធាតុក្រាហ្វិក"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"បានបិទបញ្ជីធាតុក្រាហ្វិក"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"បញ្ចូលទៅក្នុងអេក្រង់ដើម"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1087e8a..b2aa1d9 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -47,10 +47,10 @@
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ಹುಡುಕಿ"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"ಹುಡುಕಾಟ ಪೆಟ್ಟಿಗೆಯಿಂದ ಪಠ್ಯವನ್ನು ತೆರವುಗೊಳಿಸಿ"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"ವಿಜೆಟ್ಗಳು ಮತ್ತು ಶಾರ್ಟ್ಕಟ್ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
- <string name="no_search_results" msgid="3787956167293097509">"ಯಾವುದೇ ವಿಜೆಟ್ಗಳು ಅಥವಾ ಶಾರ್ಟ್ಕಟ್ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
+ <string name="no_search_results" msgid="3787956167293097509">"ಯಾವುದೇ ವಿಜೆಟ್ಗಳು ಅಥವಾ ಶಾರ್ಟ್ಕಟ್ಗಳು ಪತ್ತೆಯಾಗಿಲ್ಲ"</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"ವೈಯಕ್ತಿಕ"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ಕೆಲಸ"</string>
- <string name="widget_category_conversations" msgid="8894438636213590446">"ಸಂವಾದಗಳು"</string>
+ <string name="widget_category_conversations" msgid="8894438636213590446">"ಸಂಭಾಷಣೆಗಳು"</string>
<string name="widget_education_header" msgid="4874760613775913787">"ನಿಮ್ಮ ಬೆರಳ ತುದಿಯಲ್ಲಿ ಉಪಯುಕ್ತ ಮಾಹಿತಿ"</string>
<string name="widget_education_content" msgid="1731667670753497052">"ಆ್ಯಪ್ಗಳನ್ನು ತೆರೆಯದೆಯೇ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು, ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ನೀವು ವಿಜೆಟ್ಗಳನ್ನು ಸೇರಿಸಬಹುದು"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ, <xliff:g id="PROGRESS">%2$s</xliff:g> ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ಸ್ಥಾಪಿಸಲು ಕಾಯಲಾಗುತ್ತಿದೆ"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ಆ್ಯಪ್ ಅಪ್ಡೇಟ್ ಅಗತ್ಯವಿದೆ"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ಈ ಐಕಾನ್ಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್ಡೇಟ್ ಮಾಡಲಾಗಿಲ್ಲ. ಈ ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ಮರು-ಸಕ್ರಿಯಗೊಳಿಸಲು ನೀವು ಹಸ್ತಚಾಲಿತವಾಗಿ ಅಪ್ಡೇಟ್ ಮಾಡಬಹುದು ಅಥವಾ ಐಕಾನ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬಹುದು."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"ಅಪ್ಡೇಟ್ ಮಾಡಿ"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="widgets_list" msgid="796804551140113767">"ವಿಜೆಟ್ ಪಟ್ಟಿ"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ವಿಜೆಟ್ ಪಟ್ಟಿಯನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ಹೋಮ್ ಸ್ಕ್ರೀನ್ಗೆ ಸೇರಿಸಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 65a58f4..ac2cb02 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> 설치 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> 다운로드 중, <xliff:g id="PROGRESS">%2$s</xliff:g> 완료"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> 설치 대기 중"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"앱 업데이트 필요"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"바로가기 아이콘의 앱이 업데이트되지 않았습니다. 직접 업데이트하여 앱 바로가기를 다시 사용할 수 있도록 하거나 아이콘을 삭제하세요."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"업데이트"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"삭제"</string>
<string name="widgets_list" msgid="796804551140113767">"위젯 목록"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"위젯 목록 닫힘"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"홈 화면에 추가"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 0286ec3..98be10a 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> орнотулууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аткарылды"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> жүктөлүп алынууда, <xliff:g id="PROGRESS">%2$s</xliff:g> аяктады"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> орнотулушу күтүлүүдө"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Колдонмону жаңыртыңыз"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Бул сүрөтчөнүн колдонмосу жаңыртылган эмес. Ыкчам баскычты кайра иштетүү үчүн аны кол менен жаңыртып же сүрөтчөнү өчүрүп койсоңуз болот."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Жаңыртуу"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Өчүрүү"</string>
<string name="widgets_list" msgid="796804551140113767">"Виджеттердин тизмеси"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Виджеттердин тизмеси жабык"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Башкы экранга кошуу"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 1245f5c..9aafa18 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"ກຳລັງຕິດຕັ້ງ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳເລັດແລ້ວ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງດາວໂຫຼດ, <xliff:g id="PROGRESS">%2$s</xliff:g> ສຳເລັດ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ກຳລັງລໍຖ້າຕິດຕັ້ງ"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ຈຳເປັນຕ້ອງອັບເດດແອັບ"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ບໍ່ໄດ້ອັບເດດແອັບສຳລັບໄອຄອນນີ້. ທ່ານສາມາດອັບເດດເອງໄດ້ເພື່ອເປີດການນຳໃຊ້ທາງລັດນີ້ຄືນໃໝ່ ຫຼື ລຶບໄອຄອນດັ່ງກ່າວອອກ."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"ອັບເດດ"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ລຶບອອກ"</string>
<string name="widgets_list" msgid="796804551140113767">"ລາຍຊື່ວິດເຈັດ"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ປິດລາຍຊື່ວິດເຈັດແລ້ວ"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ເພີ່ມໃສ່ໂຮມສະກຣີນ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4e46c7b..888a8f7 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Įdiegiama: „<xliff:g id="NAME">%1$s</xliff:g>“; baigta: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Atsisiunčiama programa „<xliff:g id="NAME">%1$s</xliff:g>“, <xliff:g id="PROGRESS">%2$s</xliff:g> baigta"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Laukiama, kol bus įdiegta programa „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Būtina atnaujinti programą"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Šios piktogramos programa neatnaujinta. Galite patys atnaujinti, kad iš naujo įgalintumėte šį spartųjį klavišą, arba pašalinkite piktogramą."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Atnaujinti"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Pašalinti"</string>
<string name="widgets_list" msgid="796804551140113767">"Valdiklių sąrašas"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Valdiklių sąrašas uždarytas"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Pridėti prie pagrind. ekrano"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 0675912..3127ca8 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Notiek lietotnes “<xliff:g id="NAME">%1$s</xliff:g>” instalēšana. Norise: <xliff:g id="PROGRESS">%2$s</xliff:g>."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Lietotnes <xliff:g id="NAME">%1$s</xliff:g> lejupielāde (<xliff:g id="PROGRESS">%2$s</xliff:g> pabeigti)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Notiek <xliff:g id="NAME">%1$s</xliff:g> instalēšana"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Lietotne ir jāatjaunina"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Šai ikonai paredzētā lietotne nav atjaunināta. Varat to atjaunināt manuāli, lai atkārtoti iespējotu šo saīsni, vai noņemt ikonu."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Atjaunināt"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Noņemt"</string>
<string name="widgets_list" msgid="796804551140113767">"Logrīku saraksts"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Logrīku saraksts aizvērts"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Pievienot sākuma ekrānam"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 72a4642..2d87df8 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -77,7 +77,7 @@
<string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Не предлагај апликација"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Закачи го предвидувањето"</string>
- <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирај кратенки"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирање кратенки"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"Овозможува апликацијата да додава кратенки без интервенција на корисникот."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"да чита поставки и кратенки на почетна страница"</string>
<string name="permdesc_read_settings" msgid="4208061150510996676">"Овозможува апликацијата да ги чита поставките и кратенките на почетната страница."</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Се презема <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> завршено"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека да се инсталира"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Потребно е ажурирање на апликацијата"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Апликацијата за оваа икона не е ажурирана. Може да ажурирате рачно за да повторно се овозможи кратенкава или отстранете ја иконата."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ажурирај"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Отстрани"</string>
<string name="widgets_list" msgid="796804551140113767">"Список со виџети"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Списокот со виџети е затворен"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Додај на почетниот екран"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 0c6f24b..b548002 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"വ്യക്തിഗത ആപ്പുകളുടെ ലിസ്റ്റ്"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"ഔദ്യോഗിക ആപ്പുകളുടെ ലിസ്റ്റ്"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"നീക്കംചെയ്യുക"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"അൺഇൻസ്റ്റാളുചെയ്യുക"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"അൺഇൻസ്റ്റാൾ"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"ആപ്പ് വിവരങ്ങൾ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ഇൻസ്റ്റാൾ ചെയ്യുക"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"ആപ്പ് നിർദ്ദേശിക്കരുത്"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ഇൻസ്റ്റാൾ ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ഡൗൺലോഡ് ചെയ്യുന്നു, <xliff:g id="PROGRESS">%2$s</xliff:g> പൂർത്തിയായി"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"ഇൻസ്റ്റാൾ ചെയ്യാൻ <xliff:g id="NAME">%1$s</xliff:g> കാക്കുന്നു"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ആപ്പ് അപ്ഡേറ്റ് ചെയ്യേണ്ടതുണ്ട്"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ഈ ഐക്കണിനുള്ള ആപ്പ് അപ്ഡേറ്റ് ചെയ്തിട്ടില്ല. ഈ കുറുക്കുവഴി വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാൻ നിങ്ങൾക്ക് നേരിട്ട് അപ്ഡേറ്റ് ചെയ്യാം അല്ലെങ്കിൽ ഐക്കൺ നീക്കം ചെയ്യാം."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"അപ്ഡേറ്റ് ചെയ്യുക"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"നീക്കം ചെയ്യുക"</string>
<string name="widgets_list" msgid="796804551140113767">"വിജറ്റുകളുടെ ലിസ്റ്റ്"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"വിജറ്റുകളുടെ ലിസ്റ്റ് അവസാനിപ്പിച്ചു"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ഹോം സ്ക്രീനിലേക്ക് ചേർക്കുക"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 303e3cc..a4daad1 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>-г суулгаж байна. <xliff:g id="PROGRESS">%2$s</xliff:g> дууссан"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>-г татаж байна, <xliff:g id="PROGRESS">%2$s</xliff:g> татсан"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> нь суулгахыг хүлээж байна"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Аппын шинэчлэлт шаардлагатай"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Энэ дүрс тэмдгийн аппыг шинэчлээгүй. Та энэ товчлолыг дахин идэвхжүүлэх эсвэл дүрсийг хасахын тулд гараар шинэчлэх боломжтой."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Шинэчлэх"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Хасах"</string>
<string name="widgets_list" msgid="796804551140113767">"Жижиг хэрэгслийн жагсаалт"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Жижиг хэрэгслийн жагсаалтыг хаасан"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Үндсэн нүүрэнд нэмэх"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 4498fec..30fb4ad 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करत आहे, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करण्याची प्रतिक्षा करत आहे"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"अॅप अपडेट करणे आवश्यक आहे"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"या आयकनसाठी अॅप अपडेट केलेले नाही. हा शॉटकर्ट पुन्हा सुरू करण्यासाठी तुम्ही मॅन्युअली अपडेट करू शकता किंवा आयकन काढून टाका."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"अपडेट करा"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"काढून टाका"</string>
<string name="widgets_list" msgid="796804551140113767">"विजेट सूची"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"विजेट सूची बंद केली"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रीनवर जोडा"</string>
@@ -156,7 +160,7 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"वैयक्तिक"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"कार्य"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"कामाशी संबंधित ॲप्स ही बॅज केलेली असून तुमच्या IT ॲडमिनला दृश्यमान आहेत"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"कामाशी संबंधित ॲप्स ही बॅज केलेली असून तुमच्या आयटी ॲडमिनला दृश्यमान आहेत"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"समजले"</string>
<string name="work_apps_paused_title" msgid="3040901117349444598">"कार्य ॲप्स थांबवली आहेत"</string>
<string name="work_apps_paused_body" msgid="261634750995824906">"तुमची कार्य ॲप्स तुम्हाला सूचना पाठवू शकत नाहीत, तुमची बॅटरी वापरू शकत नाहीत किंवा तुमचे स्थान अॅक्सेस करू शकत नाहीत"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 3c75a66..ddd8dc8 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> dipasang, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> memuat turun, <xliff:g id="PROGRESS">%2$s</xliff:g> selesai"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> menunggu untuk dipasang"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Kemas kini apl diperlukan"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Apl untuk ikon ini tidak dikemas kini. Anda boleh mengemas kini secara manual untuk mendayakan semula pintasan atau mengalih keluar ikon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Kemas kini"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Alih keluar"</string>
<string name="widgets_list" msgid="796804551140113767">"Senarai widget"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Senarai widget ditutup"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Tambahkan pada skrin utama"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 48edebc..ea966d3 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"တစ်ကိုယ်ရေသုံး အက်ပ်စာရင်း"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"အလုပ်သုံး အက်ပ်စာရင်း"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"ဖယ်ရှားမည်"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ဖြုတ်ရန်"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ဖယ်ရှားရန်"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"အက်ပ်အချက်အလက်"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ထည့်သွင်းရန်"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"အက်ပ်ကို အကြံမပြုပါနှင့်"</string>
@@ -124,12 +124,16 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ကို ထည့်သွင်းနေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ဒေါင်းလုဒ်လုပ်နေသည်၊ <xliff:g id="PROGRESS">%2$s</xliff:g> ပြီးပါပြီ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ကိုထည့်သွင်းရန်စောင့်နေသည်"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"အက်ပ်ကို အပ်ဒိတ်လုပ်ရန် လိုအပ်သည်"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ဤသင်္ကေတအတွက် အက်ပ်ကို အပ်ဒိတ်လုပ်မထားပါ။ ဤဖြတ်လမ်းလင့်ခ်ကို ပြန်ဖွင့်ရန် ကိုယ်တိုင်အပ်ဒိတ်လုပ်နိုင်သည် (သို့) သင်္ကေတကို ဖယ်ရှားနိုင်သည်။"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"အပ်ဒိတ်လုပ်ရန်"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ဖယ်ရှားရန်"</string>
<string name="widgets_list" msgid="796804551140113767">"ဝိဂျက်စာရင်း"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ဝိဂျက်စာရင်းကို ပိတ်ထားသည်"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ပင်မစာမျက်နှာတွင် ထည့်ရန်"</string>
<string name="action_move_here" msgid="2170188780612570250">"၎င်းအား ဤသို့ ရွှေ့ပါ"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"ပင်မ ဖန်မျက်နှာပြင်သို့ ထည့်ပြီး၏"</string>
- <string name="item_removed" msgid="851119963877842327">"၎င်းအား ဖယ်ရှားပြီး၏"</string>
+ <string name="item_removed" msgid="851119963877842327">"ဖယ်ရှားပြီးပြီ"</string>
<string name="undo" msgid="4151576204245173321">"နောက်ပြန်ရန်"</string>
<string name="action_move" msgid="4339390619886385032">"၎င်းအား ရွှေ့ပါ"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"အတန်း <xliff:g id="NUMBER_0">%1$s</xliff:g> အတိုင် <xliff:g id="NUMBER_1">%2$s</xliff:g> သို့ ရွှေ့ပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index ad6a3cc..d55c484 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installerer, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Laster ned <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> er fullført"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Venter på å installere <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Appen må oppdateres"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Appen for dette ikonet er ikke oppdatert. Du kan oppdatere manuelt for å aktivere denne snarveien igjen, eller du kan fjerne ikonet."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Oppdater"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Fjern"</string>
<string name="widgets_list" msgid="796804551140113767">"Modulliste"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Modullisten er lukket"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Legg til på startskjermen"</string>
@@ -156,7 +160,7 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlig"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Jobb"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbapper er merket og synlige for IT-administratoren din"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbapper er merket og synlige for IT-administratoren"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"Greit"</string>
<string name="work_apps_paused_title" msgid="3040901117349444598">"Jobbapper er satt på pause"</string>
<string name="work_apps_paused_body" msgid="261634750995824906">"Jobbapper kan ikke sende deg varsler, bruke batteriet eller få tilgang til posisjonen din"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 05a284f..01f0f54 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"व्यक्तिगत अनुप्रयोगहरूको सूची"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"कार्यसम्बन्धी अनुप्रयोगहरूको सूची"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"हटाउनुहोस्"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"विस्थापित गर्नुहोस्"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइन्स्टल गर्नुहोस्"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"एपसम्बन्धी जानकारी"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"स्थापना गर्नुहोस्"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"यो एप सिफारिस नगरियोस्"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इन्स्टल गरिँदै छ, <xliff:g id="PROGRESS">%2$s</xliff:g> पूरा भयो"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड गर्दै, <xliff:g id="PROGRESS">%2$s</xliff:g> सम्पन्न"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> स्थापना गर्न प्रतीक्षा गर्दै"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"एप अपडेट गरिनु पर्छ"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"यो आइकनले जनाउने एप अपडेट गरिएको छैन। तपाईं यो सर्टकट फेरि अन गर्न म्यानुअल रूपमा अपडेट गर्न सक्नुहुन्छ वा आइकन नै हटाउनुहोस्।"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"अपडेट गर्नुहोस्"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"हटाउनुहोस्"</string>
<string name="widgets_list" msgid="796804551140113767">"विजेटहरूको सूची"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"विजेटहरूको सूची बन्द गरियो"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रिनमा राख्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index baf40ae..4289d05 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeren, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> wordt gedownload, <xliff:g id="PROGRESS">%2$s</xliff:g> voltooid"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> wacht op installatie"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"App-update vereist"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"De app voor dit icoon is niet geüpdatet. Je kunt handmatig updaten om deze snelkoppeling weer aan te zetten of het icoon verwijderen."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Updaten"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Verwijderen"</string>
<string name="widgets_list" msgid="796804551140113767">"Lijst met widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lijst met widgets gesloten"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Toevoegen aan startscherm"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 6a6b76b..00e33b8 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ କରାଯାଉଛି, <xliff:g id="PROGRESS">%2$s</xliff:g> ସମ୍ପୂର୍ଣ୍ଣ ହୋଇଛି"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ଡାଉନଲୋଡ୍ ହେଉଛି, <xliff:g id="PROGRESS">%2$s</xliff:g> ସମ୍ପୂର୍ଣ୍ଣ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ ହେବାକୁ ଅପେକ୍ଷା କରିଛି"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ଆପକୁ ଅପଡେଟ କରିବା ଆବଶ୍ୟକ"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ଏହି ଆଇକନ ପାଇଁ ଆପକୁ ଅପଡେଟ କରାଯାଇନାହିଁ। ଏହି ସର୍ଟକଟକୁ ପୁଣି-ସକ୍ଷମ କରିବା ପାଇଁ ଆପଣ ମାନୁଆଲୀ ଅପଡେଟ କରିପାରିବେ କିମ୍ବା ଆଇକନଟିକୁ କାଢ଼ି ଦେଇପାରିବେ।"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"ଅପଡେଟ କରନ୍ତୁ"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="widgets_list" msgid="796804551140113767">"ୱିଜେଟ୍ ତାଲିକା"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ୱିଜେଟ୍ ତାଲିକା ବନ୍ଦ ହୋଇଛି"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ମୂଳସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 797b71f..5dae6a4 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ਨੂੰ ਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਪੂਰਾ ਹੋਇਆ"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ਡਾਉਨਲੋਡ ਹੋਰ ਰਿਹਾ ਹੈ, <xliff:g id="PROGRESS">%2$s</xliff:g> ਸੰਪੂਰਣ"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ਸਥਾਪਤ ਕਰਨ ਦੀ ਉਡੀਕ ਕਰ ਰਿਹਾ ਹੈ"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ਇਸ ਪ੍ਰਤੀਕ ਲਈ ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਜਾਂ ਪ੍ਰਤੀਕ ਨੂੰ ਹਟਾਉਣ ਲਈ ਤੁਸੀਂ ਹੱਥੀਂ ਅੱਪਡੇਟ ਕਰ ਸਕਦੇ ਹੋ।"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"ਅੱਪਡੇਟ ਕਰੋ"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ਹਟਾਓ"</string>
<string name="widgets_list" msgid="796804551140113767">"ਵਿਜੇਟਾਂ ਦੀ ਸੂਚੀ"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ਵਿਜੇਟਾਂ ਦੀ ਸੂਚੀ ਬੰਦ ਕੀਤੀ ਗਈ"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 1f226b3..ceca83c 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instaluję aplikację <xliff:g id="NAME">%1$s</xliff:g>, postęp: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Pobieranie elementu <xliff:g id="NAME">%1$s</xliff:g>, ukończono: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> oczekuje na instalację"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Wymagana aktualizacja aplikacji"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacja z tą ikoną nie jest aktualizowana. Możesz zaktualizować ją ręcznie, aby ponownie uruchomić ten skrót, lub usunąć ikonę."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Aktualizuj"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Usuń"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista widgetów"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista widgetów zamknięta"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Dodaj do ekranu głównego"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index ddc1031..61c1ee0 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"A instalar <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"A transferir o <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"A aguardar a instalação do <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Atualização da app necessária"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"A app deste ícone não está atualizada. Pode atualizar manualmente para reativar este atalho ou remover o ícone."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Atualizar"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remover"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets fechada."</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Adicionar ao ecrã principal"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a008761..340934c 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Instalando <xliff:g id="NAME">%1$s</xliff:g>. <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Fazendo download de <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> concluído"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aguardando instalação de <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Atualização obrigatória do app"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"O app desse ícone não está atualizado. Você pode remover o ícone ou atualizar o app manualmente para reativar esse atalho."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Atualizar"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Remover"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista de widgets"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets fechada"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Adicionar à tela inicial"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index e2c9a62..43462d8 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se instalează, <xliff:g id="PROGRESS">%2$s</xliff:g> finalizat"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> se descarcă (finalizat <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> așteaptă instalarea"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Este necesară actualizarea aplicației"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplicația pentru această pictogramă nu este actualizată. Puteți să actualizați manual ca să reactivați comanda rapidă sau să eliminați pictograma."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Actualizați"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Eliminați"</string>
<string name="widgets_list" msgid="796804551140113767">"Listă de widgeturi"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgeturi este închisă"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Adăugați pe ecranul de pornire"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 373e8ed..0fa3280 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Установка приложения \"<xliff:g id="NAME">%1$s</xliff:g>\" (выполнено <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Скачивается \"<xliff:g id="NAME">%1$s</xliff:g>\" (<xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Ожидание установки \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Обновите приложение"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Эта версия приложения устарела. Обновите его вручную, чтобы снова пользоваться ярлыком, или удалите значок."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Обновить"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Удалить"</string>
<string name="widgets_list" msgid="796804551140113767">"Список виджетов"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Список виджетов закрыт"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Добавить на главный экран"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 868451d..f77746a 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> බාගත කරමින්, <xliff:g id="PROGRESS">%2$s</xliff:g> සම්පූර්ණයි"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ස්ථාපනය කිරීමට බලා සිටිමින්"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"යෙදුම් යාවත්කාලීනයක් අවශ්යයි"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"මෙම නිරූපකය සඳහා යෙදුම යාවත්කාලීන කර නැත. ඔබට මෙම කෙටි මඟ යළි සබල කිරීමට හෝ නිරූපකය ඉවත් කිරීමට හස්තීයව යාවත්කාලීන කළ හැකිය."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"යාවත්කාලීන කරන්න"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ඉවත් කරන්න"</string>
<string name="widgets_list" msgid="796804551140113767">"විජට් ලැයිස්තුව"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"විජට් ලැයිස්තුව වසා ඇත"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"මුල් තිරය වෙත එක් කරන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7ae936c..212e5f3 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Inštaluje sa <xliff:g id="NAME">%1$s</xliff:g>. Dokončené: <xliff:g id="PROGRESS">%2$s</xliff:g>."</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Sťahuje sa aplikácia <xliff:g id="NAME">%1$s</xliff:g>. Stiahnuté: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikácia <xliff:g id="NAME">%1$s</xliff:g> čaká na inštaláciu"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Vyžaduje sa aktualizácia aplikácie"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikácia, ktorú zastupuje táto ikona, nie je aktualizovaná. Môžete ju ručne aktualizovať, aby odkaz znova fungoval, prípadne môžete ikonu odstrániť."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Aktualizovať"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Odstrániť"</string>
<string name="widgets_list" msgid="796804551140113767">"Zoznam miniaplikácií"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Zoznam miniaplikácií je zavretý"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Pridať na plochu"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index cb59a99..6debc8a 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -124,12 +124,16 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> se namešča, dokončano: <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Prenašanje aplikacije <xliff:g id="NAME">%1$s</xliff:g>; preneseno <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Aplikacija <xliff:g id="NAME">%1$s</xliff:g> čaka na namestitev"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Zahtevana je posodobitev aplikacije"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacija za to ikono ni posodobljena. Lahko jo ročno posodobite, da znova omogočite to bližnjico, ali pa odstranite ikono."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Posodobi"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Odstrani"</string>
<string name="widgets_list" msgid="796804551140113767">"Seznam pripomočkov"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Seznam pripomočkov se je zaprl"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Dodajanje na začetni zaslon"</string>
<string name="action_move_here" msgid="2170188780612570250">"Premik elementa sem"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Element je bil dodan na začetni zaslon"</string>
- <string name="item_removed" msgid="851119963877842327">"Element je bil odstranjen"</string>
+ <string name="item_removed" msgid="851119963877842327">"Element je bil odstranjen."</string>
<string name="undo" msgid="4151576204245173321">"Razveljavi"</string>
<string name="action_move" msgid="4339390619886385032">"Premik elementa"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"Premik v <xliff:g id="NUMBER_0">%1$s</xliff:g>. vrstico <xliff:g id="NUMBER_1">%2$s</xliff:g>. stolpca"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index d385ade..9931752 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> po instalohet, <xliff:g id="PROGRESS">%2$s</xliff:g> i përfunduar"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> po shkarkohet, <xliff:g id="PROGRESS">%2$s</xliff:g> të përfunduara"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> po pret të instalohet"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Kërkohet përditësimi i aplikacionit"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Aplikacioni për këtë ikonë nuk është përditësuar. Mund ta përditësosh manualisht për të riaktivizuar këtë shkurtore ose hiq ikonën."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Përditëso"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Hiq"</string>
<string name="widgets_list" msgid="796804551140113767">"Lista e miniaplikacioneve"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Lista e miniaplikacioneve u mbyll"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Shto në ekranin bazë"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 0c14b56..d5f1bd2 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -47,7 +47,7 @@
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Претражите"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Обришите текст из оквира за претрагу"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"Виџети и пречице нису доступни"</string>
- <string name="no_search_results" msgid="3787956167293097509">"Није пронађен ниједан виџет или пречица"</string>
+ <string name="no_search_results" msgid="3787956167293097509">"Није пронађен ниједан виџет ни пречица"</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Лично"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Посао"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Конверзације"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> се инсталира, <xliff:g id="PROGRESS">%2$s</xliff:g> готово"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> се преузима, завршено је <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> чека на инсталирање"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Треба да ажурирате апликацију"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Апликација за ову икону није ажурирана. Можете да је ручно ажурирате да бисте поново омогућили ову пречицу или уклоните икону."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Ажурирај"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Уклони"</string>
<string name="widgets_list" msgid="796804551140113767">"Листа виџета"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Листа виџета је затворена"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Додајте на почетни екран"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 4448c30..a3f8de4 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> installeras. <xliff:g id="PROGRESS">%2$s</xliff:g> har slutförts"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> laddas ned, <xliff:g id="PROGRESS">%2$s</xliff:g> klart"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> väntar på installation"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Du måste uppdatera appen"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Appen för den här ikonen har inte uppdaterats. Du kan uppdatera den manuellt för att återaktivera genvägen eller ta bort ikonen."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Uppdatera"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Ta bort"</string>
<string name="widgets_list" msgid="796804551140113767">"Widgetlista"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widgetslistan har stängts"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Lägg till på startskärmen"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index b81fbeb..0a4e510 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"Orodha ya programu za binafsi"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"Orodha ya programu za kazini"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Ondoa"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Sakinua"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Ondoa"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Maelezo ya programu"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"Sakinisha"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Isipendekeze programu"</string>
@@ -102,7 +102,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g> au zaidi"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Mandhari"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Mandhari na muundo"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Mandhari na mtindo"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Mipangilio ya mwanzo"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Imezimwa na msimamizi wako"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Ruhusu kipengele cha kuzungusha skrini ya kwanza"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Inasakinisha <xliff:g id="NAME">%1$s</xliff:g>, imekamilika <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> inapakuliwa, <xliff:g id="PROGRESS">%2$s</xliff:g> imekamilika"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> inasubiri kusakinisha"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Unahitaji kusasisha programu"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Programu ya aikoni hii haijasasishwa. Unaweza kusasisha mwenyewe ili uruhusu upya njia hii ya mkato au uondoe aikoni."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Sasisha"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Ondoa"</string>
<string name="widgets_list" msgid="796804551140113767">"Orodha ya wijeti"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Orodha ya wijeti imefungwa"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Weka kwenye skrini ya kwanza"</string>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index dce09e3..63970cd 100644
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -16,6 +16,9 @@
-->
<resources>
+<!-- PagedView -->
+ <dimen name="min_page_snap_velocity">3600dp</dimen>
+
<!-- Hotseat -->
<dimen name="spring_loaded_hotseat_top_margin">44dp</dimen>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..072b92d
--- /dev/null
+++ b/res/values-sw600dp/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<resources>
+
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_pageSnapAnimationDuration">550</integer>
+
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 602dd6d..d69e777 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -15,6 +15,9 @@
-->
<resources>
+<!-- PagedView-->
+ <dimen name="min_page_snap_velocity">3000dp</dimen>
+
<!-- DragController -->
<dimen name="drag_flingToDeleteMinVelocity">-1000dp</dimen>
@@ -38,10 +41,10 @@
<!-- Hotseat -->
<dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
- <dimen name="spring_loaded_hotseat_top_margin">97dp</dimen>
+ <dimen name="spring_loaded_hotseat_top_margin">65dp</dimen>
<!-- Dragging -->
- <dimen name="drop_target_top_margin">34dp</dimen>
+ <dimen name="drop_target_top_margin">64dp</dimen>
<dimen name="drop_target_bottom_margin">16dp</dimen>
<dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
<dimen name="drop_target_button_drawable_vertical_padding">16dp</dimen>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
index 439ea93..235631d 100644
--- a/res/values-sw720dp-land/dimens.xml
+++ b/res/values-sw720dp-land/dimens.xml
@@ -15,6 +15,9 @@
-->
<resources>
+<!-- PagedView -->
+ <dimen name="min_page_snap_velocity">5300dp</dimen>
+
<!-- Dynamic grid -->
<dimen name="dynamic_grid_edge_margin">21.93dp</dimen>
<dimen name="cell_layout_padding">29.33dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 3ec211a..7b2ed8b 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -15,6 +15,9 @@
-->
<resources>
+<!-- PagedView -->
+ <dimen name="min_page_snap_velocity">3400dp</dimen>
+
<!-- AllApps -->
<dimen name="all_apps_bottom_sheet_horizontal_padding">28dp</dimen>
@@ -28,11 +31,12 @@
<dimen name="drop_target_button_drawable_horizontal_padding">24dp</dimen>
<dimen name="drop_target_button_drawable_vertical_padding">20dp</dimen>
<dimen name="drop_target_button_gap">32dp</dimen>
- <dimen name="drop_target_top_margin">32dp</dimen>
- <dimen name="drop_target_bottom_margin">32dp</dimen>
+ <dimen name="drop_target_button_workspace_edge_gap">32dp</dimen>
+ <dimen name="drop_target_top_margin">110dp</dimen>
+ <dimen name="drop_target_bottom_margin">48dp</dimen>
<!-- Hotseat -->
- <dimen name="spring_loaded_hotseat_top_margin">164dp</dimen>
+ <dimen name="spring_loaded_hotseat_top_margin">108dp</dimen>
<!-- Widget picker-->
<dimen name="widget_list_horizontal_margin">30dp</dimen>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 9c012f5..e7b5c9b 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> நிறுவப்படுகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g>ஐப் பதிவிறக்குகிறது, <xliff:g id="PROGRESS">%2$s</xliff:g> முடிந்தது"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>ஐ நிறுவுவதற்காகக் காத்திருக்கிறது"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ஆப்ஸைப் புதுப்பியுங்கள்"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"இந்த ஐகானுக்கான ஆப்ஸ் புதுப்பிக்கப்படவில்லை. இந்த ஷார்ட்கட்டை மீண்டும் இயக்கவோ ஐகானை அகற்றவோ நீங்களாகவே புதுப்பிக்கலாம்."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"புதுப்பி"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"அகற்று"</string>
<string name="widgets_list" msgid="796804551140113767">"விட்ஜெட்கள் பட்டியல்"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"விட்ஜெட்கள் பட்டியல் மூடப்பட்டது"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"முகப்புத் திரையில் சேர்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 4856915..a866da9 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -72,7 +72,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"వ్యక్తిగత యాప్ల లిస్ట్"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"కార్యాలయ యాప్ల లిస్ట్"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"తీసివేయి"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయి"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయండి"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్స్టాల్ చేయండి"</string>
<string name="dismiss_prediction_label" msgid="3357562989568808658">"యాప్ను సూచించవద్దు"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g>ను ఇన్స్టాల్ చేయడం, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> డౌన్లోడ్ అవుతోంది, <xliff:g id="PROGRESS">%2$s</xliff:g> పూర్తయింది"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ఇన్స్టాల్ కావడానికి వేచి ఉంది"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"యాప్ను అప్డేట్ చేయడం అవసరం"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"ఈ చిహ్నం కోసం యాప్ అప్డేట్ చేయబడలేదు. మీరు ఈ షార్ట్కట్ను మళ్లీ ఎనేబుల్ చేయడానికి మాన్యువల్గా అప్డేట్ చేయవచ్చు లేదా చిహ్నాన్ని తీసివేయవచ్చు."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"అప్డేట్ చేయండి"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"తీసివేయండి"</string>
<string name="widgets_list" msgid="796804551140113767">"విడ్జెట్ల లిస్ట్"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్ల లిస్ట్ మూసివేయబడింది"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"మొదటి స్క్రీన్కు జోడించండి"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 2d2c0b2..aef06da 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"กำลังติดตั้ง <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"กำลังดาวน์โหลด <xliff:g id="NAME">%1$s</xliff:g> เสร็จแล้ว <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> กำลังรอติดตั้ง"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ต้องอัปเดตแอป"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"แอปสำหรับไอคอนนี้ยังไม่ได้อัปเดต คุณอัปเดตด้วยตนเองได้โดยเปิดใช้ทางลัดนี้อีกครั้งหรือนำไอคอนออก"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"อัปเดต"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"นำออก"</string>
<string name="widgets_list" msgid="796804551140113767">"รายการวิดเจ็ต"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ปิดรายการวิดเจ็ตแล้ว"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"เพิ่มลงในหน้าจอหลัก"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index c87c37a..572a38f 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Ini-install ang <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> kumpleto"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Dina-download na ang <xliff:g id="NAME">%1$s</xliff:g>, tapos na ang <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Hinihintay nang mag-install ang <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Kinakailangang i-update ang app"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Hindi updated ang app para sa icon na ito. Puwede kang manual na mag-update para ma-enable ulit ang shortcut na ito, o alisin ang icon."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"I-update"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Alisin"</string>
<string name="widgets_list" msgid="796804551140113767">"Listahan ng mga widget"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Nakasara ang listahan ng mga widget"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Idagdag sa home screen"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index ae4c310..7582461 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -52,7 +52,7 @@
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"Görüşmeler"</string>
<string name="widget_education_header" msgid="4874760613775913787">"Faydalı bilgiler parmaklarınızın ucunda"</string>
- <string name="widget_education_content" msgid="1731667670753497052">"Uygulamaları açmadan bilgi almak için ana ekranınıza widget\'lar ekleyebilirsiniz"</string>
+ <string name="widget_education_content" msgid="1731667670753497052">"Uygulama açmadan bilgi almak için ana ekranınıza widget ekleyebilirsiniz"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Widget ayarlarını değiştirmek için dokunun"</string>
<string name="widget_education_close_button" msgid="8676165703104836580">"Anladım"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Widget ayarlarını değiştir"</string>
@@ -124,12 +124,16 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> yükleniyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> indiriliyor, <xliff:g id="PROGRESS">%2$s</xliff:g> tamamlandı"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> uygulaması yüklenmek için bekliyor"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Uygulama güncellemesi gerekli"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Bu simgenin uygulaması güncellenmemiş. Simgeyi kaldırabilir ya da uygulamayı manuel olarak güncelleyerek bu kısayolu yeniden etkinleştirebilirsiniz."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Güncelle"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Kaldır"</string>
<string name="widgets_list" msgid="796804551140113767">"Widget listesi"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Widget listesi kapalı"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Ana ekrana ekle"</string>
<string name="action_move_here" msgid="2170188780612570250">"Öğeyi buraya taşı"</string>
<string name="item_added_to_workspace" msgid="4211073925752213539">"Öğe ana ekrana eklendi"</string>
- <string name="item_removed" msgid="851119963877842327">"Öğe kaldırıldı"</string>
+ <string name="item_removed" msgid="851119963877842327">"Öğe silindi"</string>
<string name="undo" msgid="4151576204245173321">"Geri al"</string>
<string name="action_move" msgid="4339390619886385032">"Öğeyi taşı"</string>
<string name="move_to_empty_cell" msgid="2833711483015685619">"<xliff:g id="NUMBER_0">%1$s</xliff:g>. satır <xliff:g id="NUMBER_1">%2$s</xliff:g>. sütuna taşı"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index e6f6682..cacf898 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> встановлюється, виконано <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> завантажується, <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> очікує на завантаження"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Потрібно оновити додаток"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Додаток для цього значка не оновлено. Ви можете оновити його вручну, щоб знову ввімкнути цю швидку команду, або можете вилучити значок."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Оновити"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Вилучити"</string>
<string name="widgets_list" msgid="796804551140113767">"Список віджетів"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Список віджектів закрито"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Додати на головний екран"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 32a4108..67d0489 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال کی جا رہی ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گئی"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ڈاؤن لوڈ ہو رہا ہے، <xliff:g id="PROGRESS">%2$s</xliff:g> مکمل ہو گیا"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> انسٹال ہونے کا انتظار کر رہی ہے"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"ایپ کی اپ ڈیٹ درکار ہے"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"اس آئیکن کیلئے ایپ کو اپ ڈیٹ نہیں کیا گیا ہے۔ آپ اس شارٹ کٹ کو دوبارہ فعال کرنے کے لیے دستی طور پر اپ ڈیٹ کر سکتے ہیں، یا آئیکن کو ہٹا سکتے ہیں۔"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"اپ ڈیٹ کریں"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"ہٹائیں"</string>
<string name="widgets_list" msgid="796804551140113767">"ویجیٹس کی فہرست"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"ویجیٹس کی فہرست بند کر دی گئی"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"ہوم اسکرین میں شامل کریں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 96e2189..c0267f8 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> oʻrnatlmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> yakunlandi"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> yuklab olinmoqda, <xliff:g id="PROGRESS">%2$s</xliff:g> bajarildi"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilovasi o‘rnatilishi kutilmoqda"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Ilovani yangilash zarur"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Bu belgi uchun ilova yangilanmagan. Ushbu yorliqni qayta yoqish uchun oddiy usulda yangilashingiz yoki belgini olib tashlashingiz mumkin."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Yangilash"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Olib tashlash"</string>
<string name="widgets_list" msgid="796804551140113767">"Vidjetlar ro‘yxati"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Vidjetlar ro‘yxati yopildi"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Bosh ekranga chiqarish"</string>
diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml
new file mode 100644
index 0000000..bd48468
--- /dev/null
+++ b/res/values-v33/style.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2022 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.
+*/
+-->
+
+<resources>
+ <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:listPreferredItemPaddingEnd">16dp</item>
+ <item name="android:listPreferredItemPaddingStart">24dp</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+ <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
+ <item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item>
+ </style>
+
+ <style name="Animation.SharedBackground" parent="@android:style/Animation.Activity">
+ <item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item>
+ <item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item>
+ <item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item>
+ <item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b15515f..8e47f0c 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"Đang cài đặt <xliff:g id="NAME">%1$s</xliff:g>, hoàn tất <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Đang tải xuống <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="PROGRESS">%2$s</xliff:g> hoàn tất"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"Đang chờ cài đặt <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Cần cập nhật ứng dụng"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"Ứng dụng cho biểu tượng này chưa được cập nhật. Bạn có thể cập nhật theo cách thủ công để bật lại phím tắt này hoặc xóa biểu tượng."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Cập nhật"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Xóa"</string>
<string name="widgets_list" msgid="796804551140113767">"Danh sách tiện ích"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Đã đóng danh sách tiện ích"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Thêm vào màn hình chính"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 775c495..2a80399 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"左分屏"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"右分屏"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"轻触并按住微件即可移动该微件。"</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"轻触并按住即可移动微件。"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"点按两次并按住微件即可移动该微件或使用自定义操作。"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安装<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下载<xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g>正在等待安装"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"需要更新应用"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"此图标对应的应用未更新。您可以手动更新以重新启用该快捷方式,或者移除此图标。"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"更新"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"移除"</string>
<string name="widgets_list" msgid="796804551140113767">"微件列表"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"微件列表已关闭"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"添加到主屏幕"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e0724de..2d9b463 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安裝「<xliff:g id="NAME">%1$s</xliff:g>」(已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載 <xliff:g id="NAME">%1$s</xliff:g>,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝 <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"必須更新應用程式"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"您尚未更新這個圖示代表的應用程式。您可以手動更新以重新啟用此快速鍵,或者移除圖示。"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"更新"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"移除"</string>
<string name="widgets_list" msgid="796804551140113767">"小工具清單"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"已經關閉嘅小工具清單"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"加去主畫面"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index a2914d6..5c2f3cc 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"正在安裝「<xliff:g id="NAME">%1$s</xliff:g>」(已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>)"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"正在下載「<xliff:g id="NAME">%1$s</xliff:g>」,已完成 <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"正在等待安裝「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"必須更新應用程式"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"這個圖示代表的應用程式未更新。手動更新即可重新啟用這個捷徑,你也可以移除圖示。"</string>
+ <string name="dialog_update" msgid="2178028071796141234">"更新"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"移除"</string>
<string name="widgets_list" msgid="796804551140113767">"小工具清單"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"已關閉小工具清單"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"新增至主畫面"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 79b9acd..cc09ea3 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -124,6 +124,10 @@
<string name="app_installing_title" msgid="5864044122733792085">"I-<xliff:g id="NAME">%1$s</xliff:g> iyafakwa, seyiqede <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"I-<xliff:g id="NAME">%1$s</xliff:g> iyalandwa, <xliff:g id="PROGRESS">%2$s</xliff:g> kuqediwe"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ilinde ukufakwa"</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Kudingeka isibuyekezo se-app"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"I-app yalesi sithonjana ibuyekeziwe. Ungabuyekeza mathupha ukuze uphinde unike amandla lesi sinqamuleli, noma ususe isithonjana."</string>
+ <string name="dialog_update" msgid="2178028071796141234">"Vuselela"</string>
+ <string name="dialog_remove" msgid="6510806469849709407">"Susa"</string>
<string name="widgets_list" msgid="796804551140113767">"Uhlu lwamawijethi"</string>
<string name="widgets_list_closed" msgid="6141506579418771922">"Uhlu lwamawijethi luvaliwe"</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"Faka kusikrini sasekhaya"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index dd3e08b..13f20c2 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -159,6 +159,17 @@
defaults to 2 * numHotseatIcons -->
<attr name="numExtendedHotseatIcons" format="integer" />
+ <!-- alignment of hotseat to the grid.
+ Not applicable for 3 button mode when taskbar is enabled -->
+ <!-- defaults to numColumns, if not specified -->
+ <attr name="hotseatColumnSpan" format="integer" />
+ <!-- defaults to numColumns, if not specified -->
+ <attr name="hotseatColumnSpanLandscape" format="integer" />
+ <!-- defaults to numColumns, if not specified -->
+ <attr name="hotseatColumnSpanTwoPanelLandscape" format="integer" />
+ <!-- defaults to numColumns, if not specified -->
+ <attr name="hotseatColumnSpanTwoPanelPortrait" format="integer" />
+
<attr name="dbFile" format="string" />
<attr name="defaultLayoutId" format="reference" />
<attr name="defaultSplitDisplayLayoutId" format="reference" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 0ed2d85..9aa1f03 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -1,3 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
<resources>
<!-- Miscellaneous -->
<bool name="config_largeHeap">false</bool>
@@ -25,6 +40,9 @@
<!-- The duration of the animation from search hint to text entry -->
<integer name="config_searchHintAnimationDuration">50</integer>
+ <!-- The duration of the PagedView page snap animation -->
+ <integer name="config_pageSnapAnimationDuration">750</integer>
+
<!-- View tag key used to store SpringAnimation data. -->
<item type="id" name="spring_animation_tag" />
@@ -166,6 +184,6 @@
<!-- Swipe back to home related -->
<dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
- <dimen name="swipe_back_window_scale_y_margin">80dp</dimen>
+ <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
<dimen name="swipe_back_window_corner_radius">40dp</dimen>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e5e2217..2612a7d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,6 +24,8 @@
<dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
<!-- Minimum space between workspace and hotseat in spring loaded mode -->
<dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
+ <!-- Minimum amount of next page visible in spring loaded mode -->
+ <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
<dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
<dimen name="cell_layout_padding">10.77dp</dimen>
@@ -55,17 +57,18 @@
<dimen name="workspace_page_indicator_line_height">1dp</dimen>
<dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
-<!-- Drop target bar -->
+ <!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">56dp</dimen>
<dimen name="drop_target_vertical_gap">20dp</dimen>
- <dimen name="drop_target_top_margin">36dp</dimen>
+ <dimen name="drop_target_top_margin">32dp</dimen>
<dimen name="drop_target_bottom_margin">16dp</dimen>
+ <!-- App Widget resize frame -->
<!-- Button drop target bar -->
<dimen name="button_drop_target_min_text_size">10sp</dimen>
<dimen name="button_drop_target_resize_text_increment">1sp</dimen>
-<!-- App Widget resize frame -->
+ <!-- App Widget resize frame -->
<dimen name="widget_handle_margin">13dp</dimen>
<dimen name="resize_frame_background_padding">24dp</dimen>
<dimen name="resize_frame_margin">22dp</dimen>
@@ -78,7 +81,7 @@
<dimen name="widget_reconfigure_button_size">36dp</dimen>
<dimen name="widget_reconfigure_tip_top_margin">16dp</dimen>
-<!-- Fast scroll -->
+ <!-- Fast scroll -->
<dimen name="fastscroll_track_min_width">6dp</dimen>
<dimen name="fastscroll_track_max_width">8dp</dimen>
<dimen name="fastscroll_thumb_padding">1dp</dimen>
@@ -99,8 +102,15 @@
<dimen name="fastscroll_width">58dp</dimen>
<dimen name="fastscroll_end_margin">-26dp</dimen>
+ <!-- PagedView -->
+ <dimen name="fling_threshold_velocity">500dp</dimen>
+ <dimen name="easy_fling_threshold_velocity">400dp</dimen>
+ <dimen name="min_fling_velocity">250dp</dimen>
+ <!-- The minimum velocity of a page snap after a fling gesture -->
+ <dimen name="min_page_snap_velocity">1500dp</dimen>
+
<!-- All Apps -->
- <dimen name="all_apps_starting_vertical_translate">320dp</dimen>
+ <dimen name="all_apps_starting_vertical_translate">300dp</dimen>
<dimen name="all_apps_search_bar_field_height">48dp</dimen>
<!-- all_apps_search_bar_field_height / 2 -->
<dimen name="all_apps_search_bar_content_overlap">24dp</dimen>
@@ -129,29 +139,33 @@
<dimen name="all_apps_height_extra">6dp</dimen>
<dimen name="all_apps_bottom_sheet_horizontal_padding">0dp</dimen>
<dimen name="all_apps_paged_view_top_padding">40dp</dimen>
+ <dimen name="all_apps_personal_work_tabs_vertical_margin">16dp</dimen>
+ <dimen name="all_apps_icon_drawable_padding">8dp</dimen>
<!-- The size of corner radius of the arrow in the arrow toast. -->
<dimen name="arrow_toast_corner_radius">2dp</dimen>
<dimen name="arrow_toast_elevation">2dp</dimen>
<dimen name="arrow_toast_arrow_width">10dp</dimen>
-<!-- Search bar in All Apps -->
+ <!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
<dimen name="all_apps_header_shadow_height">6dp</dimen>
<dimen name="all_apps_divider_margin_vertical">8dp</dimen>
-<!-- Floating action button inside work tab to toggle work profile -->
+ <!-- Floating action button inside work tab to toggle work profile -->
<dimen name="work_fab_height">56dp</dimen>
<dimen name="work_fab_radius">16dp</dimen>
<dimen name="work_card_padding_horizontal">10dp</dimen>
<dimen name="work_card_button_height">52dp</dimen>
<dimen name="work_fab_margin">16dp</dimen>
+ <dimen name="work_fab_margin_bottom">20dp</dimen>
<dimen name="work_mode_fab_padding">16dp</dimen>
<dimen name="work_profile_footer_padding">20dp</dimen>
<dimen name="work_edu_card_margin">16dp</dimen>
<dimen name="work_edu_card_radius">16dp</dimen>
+ <dimen name="work_edu_card_bottom_margin">26dp</dimen>
<dimen name="work_card_margin">24dp</dimen>
<!-- (x) icon button inside work edu card -->
@@ -211,13 +225,13 @@
<dimen name="shortcut_preview_padding_right">0dp</dimen>
<dimen name="shortcut_preview_padding_top">0dp</dimen>
-<!-- Pin widget dialog -->
+ <!-- Pin widget dialog -->
<dimen name="pin_widget_button_padding_horizontal">8dp</dimen>
<dimen name="pin_widget_button_padding_vertical">4dp</dimen>
<dimen name="pin_widget_button_inset_horizontal">4dp</dimen>
<dimen name="pin_widget_button_inset_vertical">6dp</dimen>
-<!-- Dragging -->
+ <!-- Dragging -->
<!-- Drag padding to add to the bottom of drop targets -->
<dimen name="drop_target_drag_padding">14dp</dimen>
<dimen name="drop_target_text_size">16sp</dimen>
@@ -226,7 +240,9 @@
<dimen name="drop_target_button_drawable_padding">8dp</dimen>
<dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
<dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
- <dimen name="drop_target_button_gap">22dp</dimen>
+ <dimen name="drop_target_button_gap">28dp</dimen>
+ <dimen name="drop_target_button_workspace_edge_gap">0dp</dimen>
+ <dimen name="drop_target_button_screen_edge_gap">28dp</dimen>
<!-- the distance an icon must be dragged before button drop targets accept it -->
<dimen name="drag_distanceThreshold">30dp</dimen>
@@ -240,7 +256,7 @@
<dimen name="spring_loaded_panel_border">2dp</dimen>
<dimen name="keyboard_drag_stroke_width">4dp</dimen>
-<!-- Folders -->
+ <!-- Folders -->
<dimen name="page_indicator_dot_size">8dp</dimen>
<dimen name="folder_cell_x_padding">9dp</dimen>
@@ -252,22 +268,22 @@
<dimen name="folder_content_padding_left_right">8dp</dimen>
<dimen name="folder_content_padding_top">16dp</dimen>
-<!-- Sizes for managed profile badges -->
+ <!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
<dimen name="profile_badge_margin">5dp</dimen>
<dimen name="profile_badge_minimum_top">2dp</dimen>
-<!-- Shadows and outlines -->
+ <!-- Shadows and outlines -->
<dimen name="blur_size_thin_outline">1dp</dimen>
<dimen name="blur_size_medium_outline">2dp</dimen>
<dimen name="blur_size_click_shadow">4dp</dimen>
<dimen name="click_shadow_high_shift">2dp</dimen>
-<!-- Pending widget -->
+ <!-- Pending widget -->
<dimen name="pending_widget_min_padding">8dp</dimen>
<dimen name="pending_widget_elevation">2dp</dimen>
-<!-- Deep shortcuts -->
+ <!-- Deep shortcuts -->
<dimen name="deep_shortcuts_elevation">2dp</dimen>
<dimen name="bg_popup_padding">2dp</dimen>
<dimen name="bg_popup_item_width">216dp</dimen>
@@ -291,17 +307,17 @@
<!-- popup_padding_start + deep_shortcut_icon_size / 2 -->
<dimen name="popup_arrow_horizontal_center_offset">26dp</dimen>
<dimen name="popup_arrow_corner_radius">2dp</dimen>
- <!-- popup_padding_start + icon_size + 10dp -->
+ <!-- popup_padding_start + deep_shortcut_icon_size + 10dp -->
<dimen name="deep_shortcuts_text_padding_start">52dp</dimen>
- <dimen name="system_shortcut_icon_size">24dp</dimen>
+ <dimen name="system_shortcut_icon_size">20dp</dimen>
<!-- popup_arrow_horizontal_center_offset - system_shortcut_icon_size / 2 -->
<dimen name="system_shortcut_margin_start">16dp</dimen>
<dimen name="system_shortcut_header_height">56dp</dimen>
<dimen name="system_shortcut_header_icon_touch_size">48dp</dimen>
- <!-- (touch_size - icon_size) / 2 -->
- <dimen name="system_shortcut_header_icon_padding">12dp</dimen>
+ <!-- (system_shortcut_header_icon_touch_size - system_shortcut_icon_size) / 2 -->
+ <dimen name="system_shortcut_header_icon_padding">14dp</dimen>
-<!-- Notifications -->
+ <!-- Notifications -->
<dimen name="bg_round_rect_radius">8dp</dimen>
<dimen name="notification_max_trans">8dp</dimen>
<dimen name="notification_space">8dp</dimen>
@@ -319,11 +335,11 @@
<dimen name="notification_main_text_padding_start">56dp</dimen>
<dimen name="horizontal_ellipsis_size">18dp</dimen>
-<!-- Overview -->
+ <!-- Overview -->
<dimen name="options_menu_icon_size">24dp</dimen>
<dimen name="options_menu_thumb_size">32dp</dimen>
-<!-- Snackbar -->
+ <!-- Snackbar -->
<dimen name="snackbar_height">48dp</dimen>
<dimen name="snackbar_content_height">32dp</dimen>
<dimen name="snackbar_padding">8dp</dimen>
@@ -335,17 +351,17 @@
<dimen name="snackbar_max_text_size">14sp</dimen>
<dimen name="snackbar_max_width">504dp</dimen>
-<!-- Developer Options -->
+ <!-- Developer Options -->
<dimen name="developer_options_filter_margins">10dp</dimen>
-<!-- Theming related -->
+ <!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>
<dimen name="dialogCornerRadius">@dimen/default_dialog_corner_radius</dimen>
<!-- Onboarding bottomsheet related -->
<dimen name="bottom_sheet_edu_padding">24dp</dimen>
-<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
+ <!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
<dimen name="taskbar_stashed_size">0dp</dimen>
<dimen name="qsb_widget_height">0dp</dimen>
@@ -356,10 +372,10 @@
<!-- Size of the maximum radius for the enforced rounded rectangles. -->
<dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
-<!-- Base Swipe Detector, speed in dp/s -->
+ <!-- Base Swipe Detector, speed in dp/s -->
<dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
-<!-- Overview placeholder to compile in Launcher3 without Quickstep -->
+ <!-- Overview placeholder to compile in Launcher3 without Quickstep -->
<dimen name="task_thumbnail_icon_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
@@ -368,29 +384,27 @@
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="overview_actions_button_spacing">0dp</dimen>
<dimen name="overview_actions_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_top_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
- <dimen name="overview_actions_margin_three_button">0dp</dimen>
+ <dimen name="overview_actions_top_margin">0dp</dimen>
<dimen name="overview_grid_side_margin">0dp</dimen>
<dimen name="overview_grid_row_spacing">0dp</dimen>
<dimen name="overview_page_spacing">0dp</dimen>
<dimen name="split_placeholder_size">72dp</dimen>
<dimen name="split_placeholder_inset">16dp</dimen>
<dimen name="split_placeholder_icon_size">44dp</dimen>
- <dimen name="task_menu_width_grid">200dp</dimen>
+ <dimen name="task_menu_width_grid">216dp</dimen>
-<!-- Workspace grid visualization parameters -->
+ <!-- Workspace grid visualization parameters -->
<dimen name="grid_visualization_rounding_radius">28dp</dimen>
<dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
<dimen name="grid_visualization_vertical_cell_spacing">6dp</dimen>
-<!-- Search results related parameters -->
+ <!-- Search results related parameters -->
<dimen name="search_row_icon_size">48dp</dimen>
<dimen name="search_row_small_icon_size">32dp</dimen>
<dimen name="padded_rounded_button_padding">8dp</dimen>
-<!-- Bottom sheet related parameters -->
+ <!-- Bottom sheet related parameters -->
<dimen name="bottom_sheet_extra_top_padding">0dp</dimen>
<dimen name="bottom_sheet_handle_width">32dp</dimen>
<dimen name="bottom_sheet_handle_height">4dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f699fca..829a21d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -277,6 +277,9 @@
<!-- Summary for Notification dots setting. Tapping this will link enable/disable notification dots feature on the home screen. [CHAR LIMIT=50] -->
<string name="notification_dots_service_title">Show notification dots</string>
+ <!-- Title for Developer Options setting. [CHAR LIMIT=50] -->
+ <string name="developer_options_title">Developer Options</string>
+
<!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=60] -->
<string name="auto_add_shortcuts_label">Add app icons to home screen</string>
<!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
@@ -303,6 +306,17 @@
<!-- Title for an app whose download has been started. -->
<string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
+
+ <!-- Title shown on the alert dialog prompting the user to update the application in market
+ in order to re-enable the disabled shortcuts -->
+ <string name="dialog_update_title">App update required</string>
+ <!-- Message shown on the alert dialog prompting the user to update the application -->
+ <string name="dialog_update_message">The app for this icon isn\'t updated. You can update manually to re-enable this shortcut, or remove the icon.</string>
+ <!-- Message for the update button in the alert dialog, will bring the user to market -->
+ <string name="dialog_update">Update</string>
+ <!-- Message for the remove button in the alert dialog -->
+ <string name="dialog_remove">Remove</string>
+
<!-- Strings for widgets & more in the popup container/bottom sheet -->
<!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
@@ -417,7 +431,7 @@
<string name="work_apps_paused_edu_accept">Got it</string>
<!-- button string shown pause work profile -->
- <string name="work_apps_pause_btn_text">Turn off work apps</string>
+ <string name="work_apps_pause_btn_text">Pause work apps</string>
<!-- button string shown enable work profile -->
<string name="work_apps_enable_btn_text">Turn on work apps</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 864bb58..2109510 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -76,7 +76,7 @@
</style>
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
- <item name="disabledIconAlpha">.254</item>
+ <item name="disabledIconAlpha">.54</item>
</style>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index dd201e5..0802552 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -34,6 +34,8 @@
launcher:minHeightDps="300"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -42,6 +44,8 @@
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
</grid-option>
@@ -63,6 +67,8 @@
launcher:minHeightDps="420"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -71,6 +77,8 @@
launcher:minHeightDps="450"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -79,6 +87,8 @@
launcher:minHeightDps="491.33"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -87,6 +97,8 @@
launcher:minHeightDps="567"
launcher:iconImageSize="54"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -95,6 +107,8 @@
launcher:minHeightDps="567"
launcher:iconImageSize="54"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
</grid-option>
@@ -116,6 +130,8 @@
launcher:minHeightDps="694"
launcher:iconImageSize="56"
launcher:iconTextSize="14.4"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -124,6 +140,8 @@
launcher:minHeightDps="694"
launcher:iconImageSize="56"
launcher:iconTextSize="14.4"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
@@ -132,6 +150,8 @@
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
</grid-option>
@@ -144,7 +164,10 @@
launcher:numFolderRows="3"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="6"
+ launcher:hotseatColumnSpanLandscape="4"
launcher:numAllAppsColumns="6"
+ launcher:isScalable="true"
+ launcher:devicePaddingId="@xml/paddings_6x5"
launcher:dbFile="launcher_6_by_5.db"
launcher:defaultLayoutId="@xml/default_workspace_6x5"
launcher:deviceCategory="tablet" >
@@ -153,14 +176,29 @@
launcher:name="Tablet"
launcher:minWidthDps="900"
launcher:minHeightDps="820"
- launcher:minCellHeight="104"
- launcher:minCellWidth="80"
+ launcher:minCellHeight="120"
+ launcher:minCellWidth="102"
+ launcher:minCellHeightLandscape="104"
+ launcher:minCellWidthLandscape="120"
launcher:iconImageSize="60"
launcher:iconTextSize="14"
- launcher:borderSpace="16"
+ launcher:borderSpaceHorizontal="16"
+ launcher:borderSpaceVertical="64"
+ launcher:borderSpaceLandscapeHorizontal="64"
+ launcher:borderSpaceLandscapeVertical="16"
+ launcher:horizontalMargin="54"
+ launcher:horizontalMarginLandscape="120"
+ launcher:allAppsCellWidth="96"
+ launcher:allAppsCellHeight="142"
+ launcher:allAppsCellWidthLandscape="126"
+ launcher:allAppsCellHeightLandscape="126"
launcher:allAppsIconSize="60"
launcher:allAppsIconTextSize="14"
- launcher:allAppsBorderSpace="16"
+ launcher:allAppsBorderSpaceHorizontal="8"
+ launcher:allAppsBorderSpaceVertical="16"
+ launcher:allAppsBorderSpaceLandscape="16"
+ launcher:hotseatBorderSpace="58"
+ launcher:hotseatBorderSpaceLandscape="50.4"
launcher:canBeDefault="true" />
</grid-option>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 90de498..8a0c909 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -53,7 +53,7 @@
<androidx.preference.PreferenceScreen
android:key="pref_developer_options"
android:persistent="false"
- android:title="Developer Options"
+ android:title="@string/developer_options_title"
android:fragment="com.android.launcher3.settings.DeveloperOptionsFragment"/>
</androidx.preference.PreferenceScreen>
diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml
new file mode 100644
index 0000000..a72f554
--- /dev/null
+++ b/res/xml/paddings_6x5.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+ <!-- Some non default screen sizes -->
+ <device-padding
+ launcher:maxEmptySpace="30dp">
+ <workspaceTopPadding
+ launcher:a="0.34"
+ launcher:b="0"/>
+ <workspaceBottomPadding
+ launcher:a="0.26"
+ launcher:b="0"/>
+ <hotseatBottomPadding
+ launcher:a="0.4"
+ launcher:b="0"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="170dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="20dp"/>
+ <workspaceBottomPadding
+ launcher:a="0.4"
+ launcher:b="0"
+ launcher:c="20dp"/>
+ <hotseatBottomPadding
+ launcher:a="0.6"
+ launcher:b="0"
+ launcher:c="20dp"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="410dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="112dp"/>
+ <workspaceBottomPadding
+ launcher:a="0.4"
+ launcher:b="0"
+ launcher:c="112dp"/>
+ <hotseatBottomPadding
+ launcher:a="0.6"
+ launcher:b="0"
+ launcher:c="112dp"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="9999dp">
+ <workspaceTopPadding
+ launcher:a="0.40"
+ launcher:c="36dp"/>
+ <workspaceBottomPadding
+ launcher:a="0.60"
+ launcher:c="36dp"/>
+ <hotseatBottomPadding
+ launcher:a="0"
+ launcher:b="36dp"/>
+ </device-padding>
+</device-paddings>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 4386f41..90869c2 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -208,6 +208,13 @@
}
/**
+ * Returns whether there is at least one view of the given type where {@link #isOpen()} == true.
+ */
+ public static boolean hasOpenView(ActivityContext activity, @FloatingViewType int type) {
+ return getOpenView(activity, type) != null;
+ }
+
+ /**
* Returns a view matching FloatingViewType, and {@link #isOpen()} may be false (if animating
* closed).
*/
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 9369bdc..b6d3fc5 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -23,7 +23,6 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -197,13 +196,6 @@
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
- if (getLayoutManager() instanceof LinearLayoutManager) {
- LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
- if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
- // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
- return;
- }
- }
scrollToPosition(0);
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 74ec7ee..878ac3b 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -53,6 +53,7 @@
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
@@ -65,12 +66,11 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.model.data.SearchActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.BubbleTextHolder;
import com.android.launcher3.views.IconLabelDotView;
import java.text.NumberFormat;
@@ -96,7 +96,6 @@
private static final int MAX_SEARCH_LOOP_COUNT = 20;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
- private static final float HIGHLIGHT_SCALE = 1.16f;
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -175,7 +174,6 @@
private HandlerRunnable mIconLoadRequest;
private boolean mEnableIconUpdateAnimation = false;
- private BubbleTextHolder mBubbleTextHolder;
public BubbleTextView(Context context) {
this(context, null, 0);
@@ -254,11 +252,18 @@
*/
public void reset() {
mDotInfo = null;
- mDotParams.color = Color.TRANSPARENT;
+ mDotParams.dotColor = Color.TRANSPARENT;
+ mDotParams.appColor = Color.TRANSPARENT;
cancelDotScaleAnim();
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
+
+ setTag(null);
+ if (mIconLoadRequest != null) {
+ mIconLoadRequest.cancel();
+ mIconLoadRequest = null;
+ }
}
private void cancelDotScaleAnim() {
@@ -356,16 +361,8 @@
setDownloadStateContentDescription(info, info.getProgressLevel());
}
- private void setItemInfo(ItemInfoWithIcon itemInfo) {
+ protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
- if (mBubbleTextHolder != null) {
- mBubbleTextHolder.onItemInfoUpdated(itemInfo);
- }
- }
-
- public void setBubbleTextHolder(
- BubbleTextHolder bubbleTextHolder) {
- mBubbleTextHolder = bubbleTextHolder;
}
@UiThread
@@ -377,7 +374,9 @@
flags |= FLAG_NO_BADGE;
}
FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
- mDotParams.color = iconDrawable.getIconColor();
+ mDotParams.appColor = iconDrawable.getIconColor();
+ mDotParams.dotColor = getContext().getResources()
+ .getColor(android.R.color.system_accent3_100, getContext().getTheme());
setIcon(iconDrawable);
applyLabel(info);
}
@@ -913,10 +912,8 @@
} else if (info instanceof WorkspaceItemInfo) {
applyFromWorkspaceItem((WorkspaceItemInfo) info);
mActivity.invalidateParent(info);
- } else if (info instanceof PackageItemInfo) {
- applyFromItemInfoWithIcon((PackageItemInfo) info);
- } else if (info instanceof SearchActionItemInfo) {
- applyFromItemInfoWithIcon((SearchActionItemInfo) info);
+ } else if (info != null) {
+ applyFromItemInfoWithIcon(info);
}
mDisableRelayout = false;
@@ -1020,19 +1017,6 @@
getIconBounds(mIconSize, bounds);
}
- private int getIconSizeForDisplay(int display) {
- DeviceProfile grid = mActivity.getDeviceProfile();
- switch (display) {
- case DISPLAY_ALL_APPS:
- return grid.allAppsIconSizePx;
- case DISPLAY_FOLDER:
- return grid.folderChildIconSizePx;
- case DISPLAY_WORKSPACE:
- default:
- return grid.iconSizePx;
- }
- }
-
public void getSourceVisualDragBounds(Rect bounds) {
getIconBounds(mIconSize, bounds);
}
@@ -1045,8 +1029,8 @@
}
private void resetIconScale() {
- if (mIcon instanceof FastBitmapDrawable) {
- ((FastBitmapDrawable) mIcon).resetScale();
+ if (mIcon != null) {
+ mIcon.resetScale();
}
}
@@ -1067,4 +1051,19 @@
args.put("count", notificationCount);
return icuCountFormat.format(args);
}
+
+ /**
+ * Starts a long press action and returns the corresponding pre-drag condition
+ */
+ public PreDragCondition startLongPressAction() {
+ PopupContainerWithArrow popup = PopupContainerWithArrow.showForIcon(this);
+ return popup != null ? popup.createPreDragCondition(true) : null;
+ }
+
+ /**
+ * Returns true if the view can show long-press popup
+ */
+ public boolean canShowLongPressPopup() {
+ return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag());
+ }
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 0b07c95..8da4f05 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,6 +24,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -49,6 +50,8 @@
private static final int[] sTempCords = new int[2];
private static final int DRAG_VIEW_DROP_DURATION = 285;
private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
+ private static final int MAX_LINES_TEXT_MULTI_LINE = 2;
+ private static final int MAX_LINES_TEXT_SINGLE_LINE = 1;
public static final int TOOLTIP_DEFAULT = 0;
public static final int TOOLTIP_LEFT = 1;
@@ -72,6 +75,8 @@
protected CharSequence mText;
protected Drawable mDrawable;
private boolean mTextVisible = true;
+ private boolean mIconVisible = true;
+ private boolean mTextMultiLine = true;
private PopupWindow mToolTip;
private int mToolTipLocation;
@@ -109,8 +114,7 @@
// drawableLeft and drawableStart.
mDrawable = getContext().getDrawable(resId).mutate();
mDrawable.setTintList(getTextColors());
- centerIcon();
- setCompoundDrawablesRelative(mDrawable, null, null, null);
+ updateIconVisibility();
}
public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -306,13 +310,49 @@
if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
mTextVisible = isVisible;
setText(newText);
- centerIcon();
- setCompoundDrawablesRelative(mDrawable, null, null, null);
- int drawablePadding = mTextVisible ? mDrawablePadding : 0;
- setCompoundDrawablePadding(drawablePadding);
+ updateIconVisibility();
}
}
+ /**
+ * Display button text over multiple lines when isMultiLine is true, single line otherwise.
+ */
+ public void setTextMultiLine(boolean isMultiLine) {
+ if (mTextMultiLine != isMultiLine) {
+ mTextMultiLine = isMultiLine;
+ setSingleLine(!isMultiLine);
+ setMaxLines(isMultiLine ? MAX_LINES_TEXT_MULTI_LINE : MAX_LINES_TEXT_SINGLE_LINE);
+ int inputType = InputType.TYPE_CLASS_TEXT;
+ if (isMultiLine) {
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+
+ }
+ setInputType(inputType);
+ }
+ }
+
+ protected boolean isTextMultiLine() {
+ return mTextMultiLine;
+ }
+
+ /**
+ * Sets the button icon visible when isVisible is true, hides it otherwise.
+ */
+ public void setIconVisible(boolean isVisible) {
+ if (mIconVisible != isVisible) {
+ mIconVisible = isVisible;
+ updateIconVisibility();
+ }
+ }
+
+ private void updateIconVisibility() {
+ if (mIconVisible) {
+ centerIcon();
+ }
+ setCompoundDrawablesRelative(mIconVisible ? mDrawable : null, null, null, null);
+ setCompoundDrawablePadding(mIconVisible && mTextVisible ? mDrawablePadding : 0);
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -324,40 +364,6 @@
hideTooltip();
}
-
- /**
- * Reduce the size of the text until it fits or reaches a minimum.
- *
- * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and
- * it diminishes by intervals defined by
- * {@code R.dimen.button_drop_target_resize_text_increment}
- * This functionality is very similar to the option
- * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because
- * the layout width is {@code WRAP_CONTENT}.
- *
- * @param availableWidth Available width in the button to fit the text, used in
- * {@code ButtonDropTarget#isTextTruncated(int)}
- * @return The biggest text size in SP that makes the text fit or if the text can't fit returns
- * the min available value
- */
- public float resizeTextToFit(int availableWidth) {
- float minSize = Utilities.pxToSp(getResources()
- .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size));
- float step = Utilities.pxToSp(getResources()
- .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment));
- float textSize = Utilities.pxToSp(getTextSize());
-
- while (textSize > minSize) {
- if (isTextTruncated(availableWidth)) {
- textSize -= step;
- setTextSize(textSize);
- } else {
- return textSize;
- }
- }
- return minSize;
- }
-
public boolean isTextTruncated(int availableWidth) {
availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
+ getCompoundDrawablePadding());
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 87bbac6..76e945d 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -65,6 +65,7 @@
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.ParcelableSparseArray;
@@ -290,7 +291,7 @@
for (int i = 0; i < mDragOutlineAnims.length; i++) {
final InterruptibleInOutAnimator anim =
- new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
+ new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
anim.getAnimator().setInterpolator(mEaseOutInterpolator);
final int thisIndex = i;
anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
@@ -2440,7 +2441,7 @@
// First we determine if things have moved enough to cause a different layout
ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
- spanX, spanY, direction, dragView, true, new ItemConfiguration());
+ spanX, spanY, direction, dragView, true, new ItemConfiguration());
setUseTempCoords(true);
if (swapSolution != null && swapSolution.isSolution) {
@@ -2477,7 +2478,7 @@
// direction vector, since we want the solution to match the preview, and it's possible
// that the exact position of the item has changed to result in a new reordering outcome.
if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
- && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
+ && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
mDirectionVector[0] = mPreviousReorderDirection[0];
mDirectionVector[1] = mPreviousReorderDirection[1];
// We reset this vector after drop
@@ -2493,7 +2494,7 @@
// Find a solution involving pushing / displacing any items in the way
ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
- spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
+ spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
// We attempt the approach which doesn't shuffle views at all
ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
@@ -2733,12 +2734,24 @@
}
public void markCellsAsOccupiedForView(View view) {
+ if (view instanceof LauncherAppWidgetHostView
+ && view.getTag() instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
+ mOccupied.markCells(info.cellX, info.cellY, info.spanX, info.spanY, true);
+ return;
+ }
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
}
public void markCellsAsUnoccupiedForView(View view) {
+ if (view instanceof LauncherAppWidgetHostView
+ && view.getTag() instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
+ mOccupied.markCells(info.cellX, info.cellY, info.spanX, info.spanY, false);
+ return;
+ }
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index af85594..4daca8b 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -7,15 +7,19 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Process;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -23,6 +27,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
+import java.util.Collections;
import java.util.List;
/**
@@ -43,6 +48,8 @@
private static final String ATTR_CONTAINER = "container";
private static final String ATTR_SCREEN = "screen";
private static final String ATTR_FOLDER_ITEMS = "folderItems";
+ private static final String ATTR_SHORTCUT_ID = "shortcutId";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
// TODO: Remove support for this broadcast, instead use widget options to send bind time options
private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
@@ -178,7 +185,6 @@
}
}
-
/**
* Shortcut parser which allows any uri and not just web urls.
*/
@@ -189,6 +195,35 @@
}
@Override
+ public int parseAndAdd(XmlPullParser parser) {
+ final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
+ final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(shortcutId)) {
+ return parseAndAddDeepShortcut(shortcutId, packageName);
+ }
+ return super.parseAndAdd(parser);
+ }
+
+ /**
+ * This method parses and adds a deep shortcut.
+ * @return item id if the shortcut is successfully added else -1
+ */
+ private int parseAndAddDeepShortcut(String shortcutId, String packageName) {
+ try {
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
+ Process.myUserHandle());
+ Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
+ mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
+ return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
+ + " and package name = " + packageName);
+ }
+ return -1;
+ }
+
+ @Override
protected Intent parseIntent(XmlPullParser parser) {
String uri = null;
try {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 72d0f59..5152217 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -108,6 +108,7 @@
public float workspaceSpringLoadShrunkTop;
public float workspaceSpringLoadShrunkBottom;
public final int workspaceSpringLoadedBottomSpace;
+ public final int workspaceSpringLoadedMinNextPageVisiblePx;
private final int extraSpace;
public int workspaceTopPadding;
@@ -158,6 +159,7 @@
public final int numShownHotseatIcons;
public int hotseatCellHeightPx;
private final int hotseatExtraVerticalSize;
+ private final boolean areNavButtonsInline;
// In portrait: size = height, in landscape: size = width
public int hotseatBarSizePx;
public int hotseatBarTopPaddingPx;
@@ -195,9 +197,7 @@
public int overviewTaskIconDrawableSizeGridPx;
public int overviewTaskThumbnailTopMarginPx;
public final int overviewActionsHeight;
- public final int overviewActionsMarginThreeButtonPx;
- public final int overviewActionsTopMarginGesturePx;
- public final int overviewActionsBottomMarginGesturePx;
+ public final int overviewActionsTopMarginPx;
public final int overviewActionsButtonSpacing;
public int overviewPageSpacing;
public int overviewRowSpacing;
@@ -215,6 +215,8 @@
public int dropTargetHorizontalPaddingPx;
public int dropTargetVerticalPaddingPx;
public int dropTargetGapPx;
+ public int dropTargetButtonWorkspaceEdgeGapPx;
+ public int dropTargetButtonScreenEdgeGapPx;
// Insets
private final Rect mInsets = new Rect();
@@ -253,7 +255,6 @@
mInsets.set(windowBounds.insets);
isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
-
// Determine device posture.
mInfo = info;
isTablet = info.isTablet(windowBounds);
@@ -263,8 +264,8 @@
// Some more constants.
context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
- ? Configuration.ORIENTATION_LANDSCAPE
- : Configuration.ORIENTATION_PORTRAIT,
+ ? Configuration.ORIENTATION_LANDSCAPE
+ : Configuration.ORIENTATION_PORTRAIT,
windowBounds);
final Resources res = context.getResources();
mMetrics = res.getDisplayMetrics();
@@ -311,7 +312,6 @@
allAppsShiftRange = isTablet
? heightPx - allAppsTopPadding
: res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
-
folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
folderContentPaddingLeftRight =
res.getDimensionPixelSize(R.dimen.folder_content_padding_left_right);
@@ -344,9 +344,15 @@
dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
R.dimen.drop_target_button_drawable_vertical_padding);
dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
+ dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
+ R.dimen.drop_target_button_workspace_edge_gap);
+ dropTargetButtonScreenEdgeGapPx = res.getDimensionPixelSize(
+ R.dimen.drop_target_button_screen_edge_gap);
workspaceSpringLoadedBottomSpace =
res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
+ workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
@@ -360,7 +366,7 @@
// We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
// might be inline in either orientations, to keep hotseat size consistent across rotation.
- boolean areNavButtonsInline = isTaskbarPresent && !isGestureMode;
+ areNavButtonsInline = isTaskbarPresent && !isGestureMode;
if (areNavButtonsInline && canQsbInline) {
numShownHotseatIcons = inv.numShrunkenHotseatIcons;
} else {
@@ -375,15 +381,14 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
if (isQsbInline) {
hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin);
- qsbWidth = calculateQsbWidth();
} else {
hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
: res.getDimensionPixelSize(
R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
+ res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
- qsbWidth = 0;
}
+
springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
R.dimen.spring_loaded_hotseat_top_margin);
hotseatBarSidePaddingEndPx =
@@ -392,9 +397,7 @@
hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
hotseatExtraVerticalSize =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
- hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
- updateHotseatIconSize(
- pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
+ updateHotseatIconSize(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
qsbBottomMarginOriginalPx = isScalableGrid
? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
@@ -408,16 +411,14 @@
overviewTaskIconDrawableSizeGridPx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
- overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
- R.dimen.overview_actions_top_margin_gesture);
- overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
- R.dimen.overview_actions_bottom_margin_gesture);
+ // In vertical bar, use the smaller task margin for the top regardless of mode.
+ overviewActionsTopMarginPx = isVerticalBarLayout()
+ ? overviewTaskMarginPx
+ : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
overviewActionsButtonSpacing = res.getDimensionPixelSize(
R.dimen.overview_actions_button_spacing);
overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
- overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
- R.dimen.overview_actions_margin_three_button);
// Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but
// overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the
// extra margin when calculating row spacing.
@@ -487,6 +488,10 @@
cellLayoutPadding);
updateWorkspacePadding();
+ // Hotseat and QSB width depends on updated cellSize and workspace padding
+ hotseatBorderSpace = calculateHotseatBorderSpace();
+ qsbWidth = calculateQsbWidth();
+
flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
R.dimen.drag_flingToDeleteMinVelocity);
@@ -497,14 +502,26 @@
new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
}
+ /**
+ * QSB width is always calculated because when in 3 button nav the width doesn't follow the
+ * width of the hotseat.
+ */
private int calculateQsbWidth() {
- int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+ if (isQsbInline) {
+ int columns = getPanelCount() * inv.numColumns;
+ return getIconToIconWidthForColumns(columns)
+ - iconSizePx * numShownHotseatIcons
+ - hotseatBorderSpace * numShownHotseatIcons;
+ } else {
+ int columns = inv.hotseatColumnSpan[mTypeIndex];
+ return getIconToIconWidthForColumns(columns);
+ }
+ }
- return cellWidthPx * columns
- + cellLayoutBorderSpacePx.x * (columns - 1)
- - (cellWidthPx - iconSizePx) // left and right cell space
- - iconSizePx * numShownHotseatIcons
- - hotseatBorderSpace * numShownHotseatIcons;
+ private int getIconToIconWidthForColumns(int columns) {
+ return columns * getCellSize().x
+ + (columns - 1) * cellLayoutBorderSpacePx.x
+ - (getCellSize().x - iconSizePx); // left and right cell space
}
private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
@@ -655,11 +672,10 @@
updateIconSize(1f, res);
updateWorkspacePadding();
- Point workspacePadding = getTotalWorkspacePadding();
// Check to see if the icons fit within the available height.
- float usedHeight = getCellLayoutHeight();
- final int maxHeight = getWorkspaceHeight(workspacePadding);
+ float usedHeight = getCellLayoutHeightSpecification();
+ final int maxHeight = getCellLayoutHeight();
float extraHeight = Math.max(0, maxHeight - usedHeight);
float scaleY = maxHeight / usedHeight;
boolean shouldScale = scaleY < 1f;
@@ -669,7 +685,8 @@
// We scale to fit the cellWidth and cellHeight in the available space.
// The benefit of scalable grids is that we can get consistent aspect ratios between
// devices.
- float usedWidth = getCellLayoutWidth() + (desiredWorkspaceHorizontalMarginPx * 2);
+ float usedWidth =
+ getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2);
// We do not subtract padding here, as we also scale the workspace padding if needed.
scaleX = availableWidthPx / usedWidth;
shouldScale = true;
@@ -678,20 +695,20 @@
if (shouldScale) {
float scale = Math.min(scaleX, scaleY);
updateIconSize(scale, res);
- extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
+ extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
}
updateAvailableFolderCellDimensions(res);
return Math.round(extraHeight);
}
- private int getCellLayoutHeight() {
+ private int getCellLayoutHeightSpecification() {
return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
+ cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
}
- private int getCellLayoutWidth() {
- int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+ private int getCellLayoutWidthSpecification() {
+ int numColumns = getPanelCount() * inv.numColumns;
return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
+ cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
}
@@ -744,13 +761,6 @@
// All apps
updateAllAppsIconSize(scale, res);
- // Hotseat
- hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale);
- if (isQsbInline) {
- qsbWidth = calculateQsbWidth();
- } else {
- qsbWidth = 0;
- }
updateHotseatIconSize(iconSizePx);
// Folder icon
@@ -758,6 +768,23 @@
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
}
+ /**
+ * Hotseat width spans a certain number of columns on scalable grids.
+ * This method calculates the space between the icons to achieve that width.
+ */
+ private int calculateHotseatBorderSpace() {
+ if (!isScalableGrid) return 0;
+ //TODO(http://b/228998082) remove this when 3 button spaces are fixed
+ if (areNavButtonsInline) {
+ return pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
+ } else {
+ int columns = inv.hotseatColumnSpan[mTypeIndex];
+ float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+ float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
+ return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
+ }
+ }
+
/**
* Updates the iconSize for allApps* variants.
@@ -766,27 +793,26 @@
allAppsBorderSpacePx = new Point(
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale),
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale));
+ // AllApps cells don't have real space between cells,
+ // so we add the border space to the cell height
+ allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale)
+ + allAppsBorderSpacePx.y;
+ // but width is just the cell,
+ // the border is added in #updateAllAppsContainerWidth
+ allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
if (isScalableGrid) {
allAppsIconSizePx =
pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
allAppsIconTextSizePx =
pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
- // AllApps cells don't have real space between cells,
- // so we add the border space to the cell height
- allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale)
- + allAppsBorderSpacePx.y;
- // but width is just the cell,
- // the border is added in #updateAllAppsContainerWidth
- allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
} else {
- float invIconSizeDp = inv.iconSize[mTypeIndex];
- float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
+ float invIconSizeDp = inv.allAppsIconSize[mTypeIndex];
+ float invIconTextSizeSp = inv.allAppsIconTextSize[mTypeIndex];
allAppsIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
allAppsIconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * scale);
- allAppsIconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
- allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
- allAppsCellHeightPx = getCellSize().y;
+ allAppsIconDrawablePaddingPx =
+ res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
}
updateAllAppsContainerWidth(res);
@@ -881,19 +907,25 @@
result = new Point();
}
- // Since we are only concerned with the overall padding, layout direction does
- // not matter.
- Point padding = getTotalWorkspacePadding();
-
- int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
- int screenWidthPx = getWorkspaceWidth(padding);
- result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
- int screenHeightPx = getWorkspaceHeight(padding);
- result.y = calculateCellHeight(screenHeightPx, cellLayoutBorderSpacePx.y, inv.numRows);
+ int shortcutAndWidgetContainerWidth =
+ getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
+ result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x,
+ inv.numColumns);
+ int shortcutAndWidgetContainerHeight =
+ getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom);
+ result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y,
+ inv.numRows);
return result;
}
/**
+ * Gets the number of panels within the workspace.
+ */
+ public int getPanelCount() {
+ return isTwoPanels ? 2 : 1;
+ }
+
+ /**
* Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
* bottom of the screen.
*/
@@ -911,7 +943,7 @@
/**
* Gets the scaled top of the workspace in px for the spring-loaded edit state.
*/
- public float getWorkspaceSpringLoadShrunkTop() {
+ public float getCellLayoutSpringLoadShrunkTop() {
workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
+ dropTargetBarBottomMarginPx;
return workspaceSpringLoadShrunkTop;
@@ -920,7 +952,7 @@
/**
* Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
*/
- public float getWorkspaceSpringLoadShrunkBottom() {
+ private float getCellLayoutSpringLoadShrunkBottom() {
int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
workspaceSpringLoadShrunkBottom =
heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -928,19 +960,41 @@
return workspaceSpringLoadShrunkBottom;
}
- public int getWorkspaceWidth() {
- return getWorkspaceWidth(getTotalWorkspacePadding());
+ /**
+ * Gets the scale of the workspace for the spring-loaded edit state.
+ */
+ public float getWorkspaceSpringLoadScale() {
+ float scale = (getCellLayoutSpringLoadShrunkBottom() - getCellLayoutSpringLoadShrunkTop())
+ / getCellLayoutHeight();
+ scale = Math.min(scale, 1f);
+
+ // Reduce scale if next pages would not be visible after scaling the workspace
+ int workspaceWidth = availableWidthPx;
+ float scaledWorkspaceWidth = workspaceWidth * scale;
+ float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
+ if (scaledWorkspaceWidth > maxAvailableWidth) {
+ scale *= maxAvailableWidth / scaledWorkspaceWidth;
+ }
+ return scale;
}
- public int getWorkspaceWidth(Point workspacePadding) {
- int cellLayoutTotalPadding =
- (isTwoPanels ? 2 : 1) * (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
- return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
+ /**
+ * Gets the width of a single Cell Layout, aka a single panel within a Workspace.
+ *
+ * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel
+ * layouts have two Cell Layouts per workspace.
+ */
+ public int getCellLayoutWidth() {
+ return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount();
}
- private int getWorkspaceHeight(Point workspacePadding) {
- return availableHeightPx - workspacePadding.y - (cellLayoutPaddingPx.top
- + cellLayoutPaddingPx.bottom);
+ /**
+ * Gets the height of a single Cell Layout, aka a single panel within a Workspace.
+ *
+ * <p>This is the height of a Workspace, less its vertical padding.
+ */
+ public int getCellLayoutHeight() {
+ return availableHeightPx - getTotalWorkspacePadding().y;
}
public Point getTotalWorkspacePadding() {
@@ -1015,34 +1069,44 @@
mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
}
} else if (isTaskbarPresent) {
+ boolean isRtl = Utilities.isRtl(context.getResources());
int hotseatHeight = workspacePadding.bottom;
int taskbarOffset = getTaskbarOffsetY();
- int additionalLeftSpace = 0;
+ // Push icons to the side
+ int additionalQsbSpace = isQsbInline ? qsbWidth + hotseatBorderSpace : 0;
- // Center the QSB with hotseat and push icons to the right
- if (isQsbInline) {
- additionalLeftSpace = qsbWidth + hotseatBorderSpace;
- }
-
+ // Center the QSB vertically with hotseat
int hotseatTopPadding = hotseatHeight - taskbarOffset - hotseatCellHeightPx;
int endOffset = ApiWrapper.getHotseatEndOffset(context);
int requiredWidth = iconSizePx * numShownHotseatIcons
+ hotseatBorderSpace * (numShownHotseatIcons - 1)
- + additionalLeftSpace;
+ + additionalQsbSpace;
- int hotseatSize = Math.min(requiredWidth, availableWidthPx - endOffset);
- int sideSpacing = (availableWidthPx - hotseatSize) / 2;
- mHotseatPadding.set(sideSpacing + additionalLeftSpace, hotseatTopPadding, sideSpacing,
- taskbarOffset);
+ int hotseatWidth = Math.min(requiredWidth, availableWidthPx - endOffset);
+ int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
+ mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, taskbarOffset);
+
+ if (isRtl) {
+ mHotseatPadding.right += additionalQsbSpace;
+ } else {
+ mHotseatPadding.left += additionalQsbSpace;
+ }
if (endOffset > sideSpacing) {
- int diff = Utilities.isRtl(context.getResources())
+ int diff = isRtl
? sideSpacing - endOffset
: endOffset - sideSpacing;
mHotseatPadding.left -= diff;
mHotseatPadding.right += diff;
}
+ } else if (isScalableGrid) {
+ int sideSpacing = (availableWidthPx - qsbWidth) / 2;
+ mHotseatPadding.set(sideSpacing,
+ hotseatBarTopPaddingPx,
+ sideSpacing,
+ hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
+ + mInsets.bottom);
} else {
// We want the edges of the hotseat to line up with the edges of the workspace, but the
// icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -1078,7 +1142,7 @@
return Math.min(qsbBottomMarginPx + taskbarSize, freeSpace);
} else {
return (int) (freeSpace * mQsbCenterFactor)
- + (isTaskbarPresent ? taskbarSize : mInsets.bottom);
+ + (isTaskbarPresent ? taskbarSize : mInsets.bottom);
}
}
@@ -1087,13 +1151,31 @@
*/
public int getTaskbarOffsetY() {
if (isQsbInline) {
- return getQsbOffsetY() + (Math.abs(hotseatQsbHeight - iconSizePx) / 2);
+ return getQsbOffsetY() - (Math.abs(hotseatQsbHeight - hotseatCellHeightPx) / 2);
} else {
return (getQsbOffsetY() - taskbarSize) / 2;
}
}
/**
+ * Returns the number of pixels required below OverviewActions excluding insets.
+ */
+ public int getOverviewActionsClaimedSpaceBelow() {
+ if (isTaskbarPresent && !isGestureMode) {
+ // Align vertically to where nav buttons are.
+ return ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
+ }
+
+ return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
+ }
+
+ /** Gets the space that the overview actions will take, including bottom margin. */
+ public int getOverviewActionsClaimedSpace() {
+ return overviewActionsTopMarginPx + overviewActionsHeight
+ + getOverviewActionsClaimedSpaceBelow();
+ }
+
+ /**
* @return the bounds for which the open folders should be contained within
*/
public Rect getAbsoluteOpenFolderBounds() {
@@ -1261,6 +1343,7 @@
writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
+ writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
@@ -1313,12 +1396,10 @@
overviewTaskIconDrawableSizeGridPx));
writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
overviewTaskThumbnailTopMarginPx));
- writer.println(prefix + pxToDpStr("overviewActionsMarginThreeButtonPx",
- overviewActionsMarginThreeButtonPx));
- writer.println(prefix + pxToDpStr("overviewActionsTopMarginGesturePx",
- overviewActionsTopMarginGesturePx));
- writer.println(prefix + pxToDpStr("overviewActionsBottomMarginGesturePx",
- overviewActionsBottomMarginGesturePx));
+ writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
+ overviewActionsTopMarginPx));
+ writer.println(prefix + pxToDpStr("overviewActionsHeight",
+ overviewActionsHeight));
writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing",
overviewActionsButtonSpacing));
writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
@@ -1329,17 +1410,27 @@
writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
writer.println(
prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
+ writer.println(prefix + pxToDpStr("dropTargetButtonWorkspaceEdgeGapPx",
+ dropTargetButtonWorkspaceEdgeGapPx));
+ writer.println(prefix + pxToDpStr("dropTargetButtonScreenEdgeGapPx",
+ dropTargetButtonScreenEdgeGapPx));
writer.println(
prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
workspaceSpringLoadShrunkBottom));
+ writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace",
+ workspaceSpringLoadedBottomSpace));
+ writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
+ workspaceSpringLoadedMinNextPageVisiblePx));
+ writer.println(
+ prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
}
private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
Configuration config = new Configuration(c.getResources().getConfiguration());
config.orientation = orientation;
- config.densityDpi = info.densityDpi;
+ config.densityDpi = info.getDensityDpi();
config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds);
return c.createConfigurationContext(config);
}
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index ec3629d..6a8ba1b 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -39,8 +39,6 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.testing.TestProtocol;
-import java.util.Arrays;
-
/*
* The top bar containing various drop targets: Delete/App Info/Uninstall.
*/
@@ -53,6 +51,8 @@
private final Runnable mFadeAnimationEndRunnable =
() -> updateVisibility(DropTargetBar.this);
+ private final Launcher mLauncher;
+
@ViewDebug.ExportedProperty(category = "launcher")
protected boolean mDeferOnDragEnd;
@@ -60,16 +60,19 @@
protected boolean mVisible = false;
private ButtonDropTarget[] mDropTargets;
+ private ButtonDropTarget[] mTempTargets;
private ViewPropertyAnimator mCurrentAnimation;
private boolean mIsVertical = true;
public DropTargetBar(Context context, AttributeSet attrs) {
super(context, attrs);
+ mLauncher = Launcher.getLauncher(context);
}
public DropTargetBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mLauncher = Launcher.getLauncher(context);
}
@Override
@@ -80,12 +83,13 @@
mDropTargets[i] = (ButtonDropTarget) getChildAt(i);
mDropTargets[i].setDropTargetBar(this);
}
+ mTempTargets = new ButtonDropTarget[getChildCount()];
}
@Override
public void setInsets(Rect insets) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = mLauncher.getDeviceProfile();
mIsVertical = grid.isVerticalBarLayout();
lp.leftMargin = insets.left;
@@ -116,10 +120,15 @@
lp.height = grid.dropTargetBarSizePx;
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
+ int verticalPadding = dp.dropTargetVerticalPaddingPx;
setLayoutParams(lp);
for (ButtonDropTarget button : mDropTargets) {
button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx);
button.setToolTipLocation(tooltipLocation);
+ button.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
+ verticalPadding);
}
}
@@ -135,36 +144,83 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
+ int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- int visibleCount = getVisibleButtonsCount();
- if (visibleCount > 0) {
- int availableWidth = width / visibleCount;
- boolean textVisible = true;
- boolean textResized = false;
- float textSize = mDropTargets[0].getTextSize();
- for (ButtonDropTarget button : mDropTargets) {
- if (button.getVisibility() == GONE) {
- continue;
- }
- if (button.isTextTruncated(availableWidth)) {
- textSize = Math.min(textSize, button.resizeTextToFit(availableWidth));
- textResized = true;
- }
- textVisible = textVisible && !button.isTextTruncated(availableWidth);
- }
+ int visibleCount = getVisibleButtons(mTempTargets);
+ if (visibleCount == 1) {
+ int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
- if (textResized) {
- for (ButtonDropTarget button : mDropTargets) {
- button.setTextSize(textSize);
- }
- }
+ ButtonDropTarget firstButton = mTempTargets[0];
+ firstButton.setTextVisible(true);
+ firstButton.setIconVisible(true);
+ firstButton.measure(widthSpec, heightSpec);
+ } else if (visibleCount == 2) {
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ int verticalPadding = dp.dropTargetVerticalPaddingPx;
+ int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
- int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
- int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- for (ButtonDropTarget button : mDropTargets) {
- if (button.getVisibility() != GONE) {
- button.setTextVisible(textVisible);
- button.measure(widthSpec, heightSpec);
+ ButtonDropTarget firstButton = mTempTargets[0];
+ firstButton.setTextVisible(true);
+ firstButton.setIconVisible(true);
+
+ ButtonDropTarget secondButton = mTempTargets[1];
+ secondButton.setTextVisible(true);
+ secondButton.setIconVisible(true);
+ secondButton.setTextMultiLine(false);
+ // Reset second button padding in case it was previously changed to multi-line text.
+ secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
+ verticalPadding);
+
+ if (dp.isTwoPanels) {
+ // Both buttons for two panel fit to the width of one Cell Layout (less
+ // half of the center gap between the buttons).
+ float scale = dp.getWorkspaceSpringLoadScale();
+ int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
+ int halfButtonGap = dp.dropTargetGapPx / 2;
+ scaledPanelWidth -= halfButtonGap / 2;
+
+ int widthSpec = MeasureSpec.makeMeasureSpec(scaledPanelWidth, MeasureSpec.AT_MOST);
+ firstButton.measure(widthSpec, heightSpec);
+ secondButton.measure(widthSpec, heightSpec);
+ } else {
+ int availableWidth;
+ int buttonGap = dp.dropTargetGapPx;
+ if (mIsVertical) {
+ // Both buttons plus the button gap do not display past the edge of the
+ // scaled workspace, less a pre-defined gap from the edge of the workspace.
+ float scale = dp.getWorkspaceSpringLoadScale();
+ int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
+ availableWidth = Math.min(
+ panelWidth - (2 * dp.dropTargetButtonWorkspaceEdgeGapPx), width);
+ } else {
+ // Both buttons plus the button gap display up to a pre-defined margin of
+ // the unscaled workspace edge.
+ availableWidth = Math.min(
+ dp.availableWidthPx - (2 * dp.dropTargetButtonScreenEdgeGapPx),
+ width);
+ }
+ int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth - buttonGap,
+ MeasureSpec.AT_MOST);
+
+ // First button's width is at most the drop target bar's total width less the button
+ // gap.
+ firstButton.measure(widthSpec, heightSpec);
+
+ int usedWidth = firstButton.getMeasuredWidth() + buttonGap;
+ int remainingWidth = availableWidth - usedWidth;
+ widthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
+ secondButton.measure(widthSpec, heightSpec);
+
+ // Remove both icons and put the second button's text on two lines if text is
+ // truncated on phones. We assume first button's text is never truncated, so it
+ // remains single-line.
+ if (secondButton.isTextTruncated(remainingWidth) && !mIsVertical) {
+ firstButton.setIconVisible(false);
+ secondButton.setIconVisible(false);
+ secondButton.setTextMultiLine(true);
+ secondButton.setPadding(secondButton.getPaddingLeft(),
+ secondButton.getPaddingTop() / 2, secondButton.getPaddingRight(),
+ secondButton.getPaddingBottom() / 2);
}
}
}
@@ -173,100 +229,79 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- int visibleCount = getVisibleButtonsCount();
+ int visibleCount = getVisibleButtons(mTempTargets);
if (visibleCount == 0) {
return;
}
- Launcher launcher = Launcher.getLauncher(getContext());
- Workspace<?> workspace = launcher.getWorkspace();
- DeviceProfile dp = launcher.getDeviceProfile();
- int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx;
- int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx;
+ DeviceProfile dp = mLauncher.getDeviceProfile();
int barCenter = (right - left) / 2;
-
- ButtonDropTarget[] visibleButtons = Arrays.stream(mDropTargets)
- .filter(b -> b.getVisibility() != GONE)
- .toArray(ButtonDropTarget[]::new);
- Arrays.stream(visibleButtons).forEach(
- b -> b.setPadding(buttonHorizontalPadding, buttonVerticalPadding,
- buttonHorizontalPadding, buttonVerticalPadding));
+ if (mIsVertical) {
+ // Center vertical bar over scaled workspace, accounting for hotseat offset.
+ float scale = dp.getWorkspaceSpringLoadScale();
+ Workspace<?> ws = mLauncher.getWorkspace();
+ int workspaceCenter = (ws.getLeft() + ws.getRight()) / 2;
+ int cellLayoutCenter = ((dp.getInsets().left + dp.workspacePadding.left) + (dp.widthPx
+ - dp.getInsets().right - dp.workspacePadding.right)) / 2;
+ int cellLayoutCenterOffset = (int) ((cellLayoutCenter - workspaceCenter) * scale);
+ barCenter = workspaceCenter + cellLayoutCenterOffset;
+ }
if (visibleCount == 1) {
- ButtonDropTarget button = visibleButtons[0];
+ ButtonDropTarget button = mTempTargets[0];
button.layout(barCenter - (button.getMeasuredWidth() / 2), 0,
barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight());
} else if (visibleCount == 2) {
int buttonGap = dp.dropTargetGapPx;
if (dp.isTwoPanels) {
- ButtonDropTarget leftButton = visibleButtons[0];
+ ButtonDropTarget leftButton = mTempTargets[0];
leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
- ButtonDropTarget rightButton = visibleButtons[1];
+ ButtonDropTarget rightButton = mTempTargets[1];
rightButton.layout(barCenter + (buttonGap / 2), 0,
- barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
+ barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
rightButton.getMeasuredHeight());
- } else if (dp.isTablet) {
- int numberOfMargins = visibleCount - 1;
- int buttonWidths = Arrays.stream(mDropTargets)
- .filter(b -> b.getVisibility() != GONE)
- .mapToInt(ButtonDropTarget::getMeasuredWidth)
- .sum();
- int totalWidth = buttonWidths + (numberOfMargins * buttonGap);
- int buttonsStartMargin = barCenter - (totalWidth / 2);
-
- int start = buttonsStartMargin;
- for (ButtonDropTarget button : visibleButtons) {
- int margin = (start != buttonsStartMargin) ? buttonGap : 0;
- button.layout(start + margin, 0, start + margin + button.getMeasuredWidth(),
- button.getMeasuredHeight());
- start += button.getMeasuredWidth() + margin;
- }
- } else if (mIsVertical) {
- // Center buttons over workspace, not screen.
- int verticalCenter = (workspace.getRight() - workspace.getLeft()) / 2;
- ButtonDropTarget leftButton = visibleButtons[0];
- leftButton.layout(verticalCenter - leftButton.getMeasuredWidth() - (buttonGap / 2),
- 0, verticalCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
-
- ButtonDropTarget rightButton = visibleButtons[1];
- rightButton.layout(verticalCenter + (buttonGap / 2), 0,
- verticalCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
- rightButton.getMeasuredHeight());
- } else if (dp.isPhone) {
- // Buttons aligned to outer edges of scaled workspace.
- float shrunkTop = dp.getWorkspaceSpringLoadShrunkTop();
- float shrunkBottom = dp.getWorkspaceSpringLoadShrunkBottom();
- float scale =
- (shrunkBottom - shrunkTop) / launcher.getWorkspace().getNormalChildHeight();
- int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
- int start = barCenter - (workspaceWidth / 2);
- int end = barCenter + (workspaceWidth / 2);
-
- ButtonDropTarget leftButton = visibleButtons[0];
- ButtonDropTarget rightButton = visibleButtons[1];
-
- // If the text within the buttons is too long, the buttons can overlap
- int overlap = start + leftButton.getMeasuredWidth() + rightButton.getMeasuredWidth()
- - end;
- if (overlap > 0) {
- end += overlap;
+ } else {
+ int start;
+ int end;
+ if (mIsVertical) {
+ // Scaled CellLayout width is assumed to not exceed the bounds of left/right.
+ float scale = dp.getWorkspaceSpringLoadScale();
+ int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
+ start = barCenter - (panelWidth / 2) + dp.dropTargetButtonWorkspaceEdgeGapPx;
+ end = barCenter + (panelWidth / 2) - dp.dropTargetButtonWorkspaceEdgeGapPx;
+ } else {
+ start = Math.max(dp.dropTargetButtonScreenEdgeGapPx, left);
+ end = Math.min(dp.availableWidthPx - dp.dropTargetButtonScreenEdgeGapPx, right);
}
- leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(),
+ ButtonDropTarget leftButton = mTempTargets[0];
+ ButtonDropTarget rightButton = mTempTargets[1];
+
+ int leftButtonWidth = leftButton.getMeasuredWidth();
+ int rightButtonWidth = rightButton.getMeasuredWidth();
+ int buttonPlusGapWidth = leftButtonWidth + buttonGap + rightButtonWidth;
+
+ int extraSpace = end - start - buttonPlusGapWidth;
+ start = (start - left) + (extraSpace / 2);
+
+ leftButton.layout(start, 0, start + leftButtonWidth,
leftButton.getMeasuredHeight());
- rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end,
+
+ int rightButtonStart = start + leftButtonWidth + buttonGap;
+ rightButton.layout(rightButtonStart, 0, rightButtonStart + rightButtonWidth,
rightButton.getMeasuredHeight());
}
}
}
- private int getVisibleButtonsCount() {
+ private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) {
int visibleCount = 0;
- for (ButtonDropTarget buttons : mDropTargets) {
- if (buttons.getVisibility() != GONE) {
+ for (ButtonDropTarget button : mDropTargets) {
+ if (button.getVisibility() != GONE) {
+ outVisibleButtons[visibleCount] = button;
visibleCount++;
}
}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index d1d6c18..76106fc 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -173,17 +173,9 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width;
- if (mActivity.getDeviceProfile().isQsbInline) {
- width = mActivity.getDeviceProfile().qsbWidth;
- } else {
- MarginLayoutParams qsbParams = (MarginLayoutParams) mQsb.getLayoutParams();
- width = getShortcutsAndWidgets().getMeasuredWidth()
- - qsbParams.getMarginStart()
- - qsbParams.getMarginEnd();
- }
+ int qsbWidth = mActivity.getDeviceProfile().qsbWidth;
- mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ mQsb.measure(MeasureSpec.makeMeasureSpec(qsbWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
}
@@ -195,7 +187,8 @@
int left;
if (mActivity.getDeviceProfile().isQsbInline) {
int qsbSpace = mActivity.getDeviceProfile().hotseatBorderSpace;
- left = l + getPaddingLeft() - qsbWidth - qsbSpace;
+ left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
+ : l + getPaddingLeft() - qsbWidth - qsbSpace;
} else {
left = (r - l - qsbWidth) / 2;
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 36c1797..db43b44 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -61,6 +61,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -152,6 +154,8 @@
*/
public int numDatabaseHotseatIcons;
+ public int[] hotseatColumnSpan;
+
/**
* Number of columns in the all apps list.
*/
@@ -355,6 +359,7 @@
numShrunkenHotseatIcons = closestProfile.numShrunkenHotseatIcons;
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
+ hotseatColumnSpan = closestProfile.hotseatColumnSpan;
hotseatBorderSpaces = displayOption.hotseatBorderSpaces;
numAllAppsColumns = closestProfile.numAllAppsColumns;
@@ -396,7 +401,8 @@
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
float parallaxFactor =
- dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.densityDpi) < 720
+ dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi())
+ < 720
? 2
: wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
defaultWallpaperSize.x =
@@ -585,8 +591,8 @@
}
}
- float width = dpiFromPx(minWidthPx, displayInfo.densityDpi);
- float height = dpiFromPx(minHeightPx, displayInfo.densityDpi);
+ float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi());
+ float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());
// Sort the profiles based on the closeness to the device size
Collections.sort(points, (a, b) ->
@@ -625,8 +631,21 @@
float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
- return getBestMatch(screenWidth, screenHeight,
- WindowManagerProxy.INSTANCE.get(context).getRotation(context));
+ int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
+
+ if (Utilities.IS_DEBUG_DEVICE) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter);
+ DisplayController.INSTANCE.get(context).dump(printWriter);
+ printWriter.flush();
+ Log.d("b/231312158", "getDeviceProfile -"
+ + "\nconfig: " + config
+ + "\ndisplayMetrics: " + res.getDisplayMetrics()
+ + "\nrotation: " + rotation
+ + "\n" + stringWriter.toString(),
+ new Exception());
+ }
+ return getBestMatch(screenWidth, screenHeight, rotation);
}
/**
@@ -720,6 +739,7 @@
private final int numHotseatIcons;
private final int numShrunkenHotseatIcons;
private final int numDatabaseHotseatIcons;
+ private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
private final String dbFile;
@@ -759,6 +779,16 @@
R.styleable.GridDisplayOption_numShrunkenHotseatIcons, numHotseatIcons / 2);
numDatabaseHotseatIcons = a.getInt(
R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
+ hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
+ R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
+ hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
+ R.styleable.GridDisplayOption_hotseatColumnSpanLandscape, numColumns);
+ hotseatColumnSpan[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
+ R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelLandscape,
+ numColumns);
+ hotseatColumnSpan[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
+ R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelPortrait,
+ numColumns);
numFolderRows = a.getInt(
R.styleable.GridDisplayOption_numFolderRows, numRows);
@@ -806,6 +836,7 @@
private float folderBorderSpace;
private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
private final float[] horizontalMargin = new float[COUNT_SIZES];
+ //TODO(http://b/228998082) remove this when 3 button spaces are fixed
private final float[] hotseatBorderSpaces = new float[COUNT_SIZES];
private final float[] iconSizes = new float[COUNT_SIZES];
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 923e614..135b88d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
@@ -40,7 +42,6 @@
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
-import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -63,7 +64,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -107,7 +107,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
@@ -128,7 +128,6 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.BaseAllAppsContainerView;
import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -183,9 +182,6 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -339,6 +335,7 @@
private Runnable mOnDeferredActivityLaunchCallback;
private ViewOnDrawExecutor mPendingExecutor;
+ private OnPreDrawListener mOnInitialBindListener;
private LauncherModel mModel;
private ModelWriter mModelWriter;
@@ -434,8 +431,7 @@
shareIntent.putExtra(Intent.EXTRA_TEXT, stackTrace);
shareIntent = Intent.createChooser(shareIntent, null);
PendingIntent sharePendingIntent = PendingIntent.getActivity(
- this, 0, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT
- );
+ this, 0, shareIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE);
Notification notification = new Notification.Builder(this, notificationChannelId)
.setSmallIcon(android.R.drawable.ic_menu_close_clear_cancel)
@@ -502,11 +498,10 @@
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
- Log.d(BAD_STATE, "Launcher onCreate not binding sync, setting DragLayer alpha "
- + "ALPHA_INDEX_LAUNCHER_LOAD to 0");
- // If we are not binding synchronously, show a fade in animation when
- // the first page bind completes.
- mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
+ Log.d(BAD_STATE, "Launcher onCreate not binding sync, prevent drawing");
+ // If we are not binding synchronously, pause drawing until initial bind complete,
+ // so that the system could continue to show the device loading prompt
+ mOnInitialBindListener = Boolean.FALSE::booleanValue;
}
}
@@ -514,25 +509,9 @@
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
setContentView(getRootView());
- getRootView().getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // Checks the status of fade in animation.
- final AlphaProperty property =
- mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
- if (property.getValue() == 0) {
- Log.d(BAD_STATE, "Launcher onPreDraw ALPHA_INDEX_LAUNCHER_LOAD not"
- + " started yet, cancelling draw.");
- // Animation haven't started yet; suspend.
- return false;
- } else {
- // The animation is started; start drawing.
- getRootView().getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- }
- });
+ if (mOnInitialBindListener != null) {
+ getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
+ }
getRootView().dispatchInsets();
// Listen for broadcasts
@@ -1190,6 +1169,7 @@
}
AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
+ DragView.removeAllViews(this);
TraceHelper.INSTANCE.endSection(traceToken);
}
@@ -1300,13 +1280,16 @@
* @param info The data structure describing the shortcut.
*/
View createShortcut(WorkspaceItemInfo info) {
- return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+ // This can be called before PagedView#pageScrollsInitialized returns true, so use the
+ // first page, which we always assume to be present.
+ return createShortcut((ViewGroup) mWorkspace.getChildAt(0), info);
}
/**
* Creates a view representing a shortcut inflated from the specified resource.
*
- * @param parent The group the shortcut belongs to.
+ * @param parent The group the shortcut belongs to. This is not necessarily the group where
+ * the shortcut should be added.
* @param info The data structure describing the shortcut.
* @return A View inflated from layoutResId.
*/
@@ -2691,36 +2674,12 @@
AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
}
- AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
- if (property.getValue() < 1) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
-
- Log.d(BAD_STATE, "Launcher onInitialBindComplete toAlpha=" + 1);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- Log.d(BAD_STATE, "Launcher onInitialBindComplete onStart");
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- float alpha = mDragLayer == null
- ? -1
- : mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).getValue();
- Log.d(BAD_STATE, "Launcher onInitialBindComplete onCancel, alpha=" + alpha);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- Log.d(BAD_STATE, "Launcher onInitialBindComplete onEnd");
- }
- });
-
- anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
- anim.start();
- } else {
- executor.onLoadAnimationCompleted();
+ if (mOnInitialBindListener != null) {
+ getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
+ mOnInitialBindListener = null;
}
+
+ executor.onLoadAnimationCompleted();
executor.attachTo(this);
if (Utilities.ATLEAST_S) {
Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
@@ -2783,9 +2742,9 @@
*/
public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user,
boolean supportsAllAppsState) {
- final ItemInfoMatcher preferredItem = (info, cn) ->
+ final Predicate<ItemInfo> preferredItem = info ->
info != null && info.id == preferredItemId;
- final ItemInfoMatcher packageAndUserAndApp = (info, cn) ->
+ final Predicate<ItemInfo> packageAndUserAndApp = info ->
info != null
&& info.itemType == ITEM_TYPE_APPLICATION
&& info.user.equals(user)
@@ -2814,8 +2773,8 @@
* @param operators List of operators, in order starting from best matching operator.
*/
private static View getFirstMatch(Iterable<ViewGroup> containers,
- final ItemInfoMatcher... operators) {
- for (ItemInfoMatcher operator : operators) {
+ final Predicate<ItemInfo>... operators) {
+ for (Predicate<ItemInfo> operator : operators) {
for (ViewGroup container : containers) {
View match = mapOverViewGroup(container, operator);
if (match != null) {
@@ -2830,11 +2789,11 @@
* Returns the first view matching the operator in the given ViewGroups, or null if none.
* Forward iteration matters.
*/
- private static View mapOverViewGroup(ViewGroup container, ItemInfoMatcher op) {
+ private static View mapOverViewGroup(ViewGroup container, Predicate<ItemInfo> op) {
final int itemCount = container.getChildCount();
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
View item = container.getChildAt(itemIdx);
- if (op.matchesInfo((ItemInfo) item.getTag())) {
+ if (op.test((ItemInfo) item.getTag())) {
return item;
}
}
@@ -2931,7 +2890,7 @@
* package-removal should clear all items by package name.
*/
@Override
- public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) {
+ public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
mWorkspace.removeItemsByMatcher(matcher);
mDragController.onAppsRemoved(matcher);
PopupContainerWithArrow.dismissInvalidPopup(this);
@@ -3212,6 +3171,24 @@
return new DragOptions();
}
+ /**
+ * Animates Launcher elements during a transition to the All Apps page.
+ *
+ * @param progress Transition progress from 0 to 1; where 0 => home and 1 => all apps.
+ */
+ public void onAllAppsTransition(float progress) {
+ // No-Op
+ }
+
+ /**
+ * Animates Launcher elements during a transition to the Widgets pages.
+ *
+ * @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets.
+ */
+ public void onWidgetsTransition(float progress) {
+ // No-Op
+ }
+
private static class NonConfigInstance {
public Configuration config;
public Bitmap snapshot;
@@ -3234,12 +3211,12 @@
/** Pauses view updates that should not be run during the app launch animation. */
public void pauseExpensiveViewUpdates() {
// Pause page indicator animations as they lead to layer trashing.
- mWorkspace.getPageIndicator().pauseAnimations();
+ getWorkspace().getPageIndicator().pauseAnimations();
}
/** Resumes view updates at the end of the app launch animation. */
public void resumeExpensiveViewUpdates() {
- mWorkspace.getPageIndicator().skipAnimationsToEnd();
+ getWorkspace().getPageIndicator().skipAnimationsToEnd();
}
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4501159..597bc8d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.config.FeatureFlags.ENABLE_THEMED_ICONS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -97,7 +99,8 @@
modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
- Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+ ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
if (FeatureFlags.IS_STUDIO_BUILD) {
modelChangeReceiver.register(mContext, Context.RECEIVER_EXPORTED, ACTION_FORCE_ROLOAD);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ee6f51e..5c5c101 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -51,6 +53,7 @@
import com.android.launcher3.model.PackageIncrementalDownloadUpdatedTask;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
+import com.android.launcher3.model.ReloadStringCacheTask;
import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.model.data.AppInfo;
@@ -278,6 +281,8 @@
user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
}
}
+ } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
+ enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
} else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
for (Callbacks cb : getCallbacks()) {
if (cb instanceof Launcher) {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index baee49f..ea6a919 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -80,6 +80,9 @@
public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(6);
public static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
+ // Flag indicating that hotseat and its contents are not accessible.
+ public static final int FLAG_HOTSEAT_INACCESSIBLE = BaseState.getFlag(8);
+
public static final float NO_OFFSET = 0;
public static final float NO_SCALE = 1;
@@ -110,7 +113,7 @@
FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
FLAG_HAS_SYS_UI_SCRIM) {
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
// Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
return 0;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f5ac359..4d33eae 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -28,6 +28,8 @@
import android.animation.LayoutTransition;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -78,27 +80,19 @@
public static final int INVALID_PAGE = -1;
protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
- public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
-
private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
// The page is moved more than halfway, automatically move to the next page on touch up.
private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
private static final float MAX_SCROLL_PROGRESS = 1.0f;
- // The following constants need to be scaled based on density. The scaled versions will be
- // assigned to the corresponding member variables below.
- private static final int FLING_THRESHOLD_VELOCITY = 500;
- private static final int EASY_FLING_THRESHOLD_VELOCITY = 400;
- private static final int MIN_SNAP_VELOCITY = 1500;
- private static final int MIN_FLING_VELOCITY = 250;
-
private boolean mFreeScroll = false;
- protected final int mFlingThresholdVelocity;
- protected final int mEasyFlingThresholdVelocity;
- protected final int mMinFlingVelocity;
- protected final int mMinSnapVelocity;
+ private int mFlingThresholdVelocity;
+ private int mEasyFlingThresholdVelocity;
+ private int mMinFlingVelocity;
+ private int mMinSnapVelocity;
+ private int mPageSnapAnimationDuration;
protected boolean mFirstLayout = true;
@@ -129,7 +123,10 @@
private boolean mAllowEasyFling;
protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
- protected int[] mPageScrolls;
+ private final ArrayList<Runnable> mOnPageScrollsInitializedCallbacks = new ArrayList<>();
+
+ // We should always check pageScrollsInitialized() is true when using mPageScrolls.
+ @Nullable protected int[] mPageScrolls = null;
private boolean mIsBeingDragged;
// The amount of movement to begin scrolling
@@ -189,11 +186,7 @@
mPageSlop = configuration.getScaledPagingTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- float density = getResources().getDisplayMetrics().density;
- mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
- mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density);
- mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
- mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
+ updateVelocityValues();
initEdgeEffect();
setDefaultFocusHighlightEnabled(false);
@@ -625,6 +618,22 @@
- mInsets.left - mInsets.right;
}
+ private void updateVelocityValues() {
+ Resources res = getResources();
+ mFlingThresholdVelocity = res.getDimensionPixelSize(R.dimen.fling_threshold_velocity);
+ mEasyFlingThresholdVelocity =
+ res.getDimensionPixelSize(R.dimen.easy_fling_threshold_velocity);
+ mMinFlingVelocity = res.getDimensionPixelSize(R.dimen.min_fling_velocity);
+ mMinSnapVelocity = res.getDimensionPixelSize(R.dimen.min_page_snap_velocity);
+ mPageSnapAnimationDuration = res.getInteger(R.integer.config_pageSnapAnimationDuration);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateVelocityValues();
+ }
+
@Override
public void requestLayout() {
mIsLayoutValid = false;
@@ -684,14 +693,37 @@
setMeasuredDimension(widthSize, heightSize);
}
+ /** Returns true iff this PagedView's scroll amounts are initialized to each page index. */
+ protected boolean pageScrollsInitialized() {
+ return mPageScrolls != null && mPageScrolls.length == getChildCount();
+ }
+
+ /**
+ * Queues the given callback to be run once {@code mPageScrolls} has been initialized.
+ */
+ public void runOnPageScrollsInitialized(Runnable callback) {
+ mOnPageScrollsInitializedCallbacks.add(callback);
+ if (pageScrollsInitialized()) {
+ onPageScrollsInitialized();
+ }
+ }
+
+ private void onPageScrollsInitialized() {
+ for (Runnable callback : mOnPageScrollsInitializedCallbacks) {
+ callback.run();
+ }
+ mOnPageScrollsInitializedCallbacks.clear();
+ }
+
@SuppressLint("DrawAllocation")
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mIsLayoutValid = true;
final int childCount = getChildCount();
+ int[] pageScrolls = mPageScrolls;
boolean pageScrollChanged = false;
- if (mPageScrolls == null || childCount != mPageScrolls.length) {
- mPageScrolls = new int[childCount];
+ if (!pageScrollsInitialized()) {
+ pageScrolls = new int[childCount];
pageScrollChanged = true;
}
@@ -701,10 +733,8 @@
if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
- boolean isScrollChanged = getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC);
- if (isScrollChanged) {
- pageScrollChanged = true;
- }
+ pageScrollChanged |= getPageScrolls(pageScrolls, true, SIMPLE_SCROLL_LOGIC);
+ mPageScrolls = pageScrolls;
final LayoutTransition transition = getLayoutTransition();
// If the transition is running defer updating max scroll, as some empty pages could
@@ -738,6 +768,7 @@
if (mScroller.isFinished() && pageScrollChanged) {
setCurrentPage(getNextPage());
}
+ onPageScrollsInitialized();
}
/**
@@ -849,8 +880,10 @@
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- mCurrentPage = validateNewPage(mCurrentPage);
- mCurrentScrollOverPage = mCurrentPage;
+ runOnPageScrollsInitialized(() -> {
+ mCurrentPage = validateNewPage(mCurrentPage);
+ mCurrentScrollOverPage = mCurrentPage;
+ });
dispatchPageCountChanged();
}
@@ -1153,7 +1186,7 @@
}
public int getScrollForPage(int index) {
- if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+ if (!pageScrollsInitialized() || index >= mPageScrolls.length || index < 0) {
return 0;
} else {
return mPageScrolls[index];
@@ -1163,7 +1196,7 @@
// While layout transitions are occurring, a child's position may stray from its baseline
// position. This method returns the magnitude of this stray at any given time.
public int getLayoutTransitionOffsetForPage(int index) {
- if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+ if (!pageScrollsInitialized() || index >= mPageScrolls.length || index < 0) {
return 0;
} else {
View child = getChildAt(index);
@@ -1440,7 +1473,7 @@
return Math.abs(velocity) > threshold;
}
- private void resetTouchState() {
+ protected void resetTouchState() {
releaseVelocityTracker();
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
@@ -1589,7 +1622,7 @@
}
protected void snapToDestination() {
- snapToPage(getDestinationPage(), PAGE_SNAP_ANIMATION_DURATION);
+ snapToPage(getDestinationPage(), mPageSnapAnimationDuration);
}
// We want the duration of the page snap animation to be influenced by the distance that
@@ -1613,7 +1646,7 @@
if (Math.abs(velocity) < mMinFlingVelocity) {
// If the velocity is low enough, then treat this more as an automatic page advance
// as opposed to an apparent physical response to flinging
- return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ return snapToPage(whichPage, mPageSnapAnimationDuration);
}
// Here we compute a "distance" that will be used in the computation of the overall
@@ -1636,11 +1669,11 @@
}
public boolean snapToPage(int whichPage) {
- return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ return snapToPage(whichPage, mPageSnapAnimationDuration);
}
public boolean snapToPageImmediately(int whichPage) {
- return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+ return snapToPage(whichPage, mPageSnapAnimationDuration, true);
}
public boolean snapToPage(int whichPage, int duration) {
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 1c36db1..f709aca 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -31,6 +31,10 @@
public static final String NAVBAR_HEIGHT = "navigation_bar_height";
public static final String NAVBAR_HEIGHT_LANDSCAPE = "navigation_bar_height_landscape";
+ public static final String STATUS_BAR_HEIGHT = "status_bar_height";
+ public static final String STATUS_BAR_HEIGHT_LANDSCAPE = "status_bar_height_landscape";
+ public static final String STATUS_BAR_HEIGHT_PORTRAIT = "status_bar_height_portrait";
+
public static int getNavbarSize(String resName, Resources res) {
return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9241c45..7b96838 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -53,6 +53,7 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
@@ -72,9 +73,9 @@
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.NonNull;
import androidx.core.graphics.ColorUtils;
-import androidx.core.os.BuildCompat;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.GridCustomizationsProvider;
@@ -122,15 +123,20 @@
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.P)
public static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.Q)
public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.R)
public static final boolean ATLEAST_R = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.S)
public static final boolean ATLEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
- public static final boolean ATLEAST_T = BuildCompat.isAtLeastT();
+ @ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
+ public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
@@ -267,6 +273,16 @@
}
/**
+ * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)}
+ * but accepts a Rect instead of float[].
+ */
+ public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) {
+ float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom};
+ mapCoordInSelfToDescendant(descendant, root, coords);
+ rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]);
+ }
+
+ /**
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
*/
public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 86e88b7..6c091f0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -50,7 +50,6 @@
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -86,14 +85,12 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.SearchActionItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
@@ -103,7 +100,6 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.OverlayEdgeEffect;
import com.android.launcher3.util.PackageUserKey;
@@ -123,7 +119,6 @@
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
@@ -202,6 +197,7 @@
private final int[] mTempXY = new int[2];
private final float[] mTempFXY = new float[2];
+ private final Rect mTempRect = new Rect();
@Thunk float[] mDragViewVisualCenter = new float[2];
private SpringLoadedDragController mSpringLoadedDragController;
@@ -908,7 +904,11 @@
* two panel UI is enabled.
*/
public int getScreenPair(int screenId) {
- if (screenId % 2 == 0) {
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ return EXTRA_EMPTY_SCREEN_SECOND_ID;
+ } else if (screenId == EXTRA_EMPTY_SCREEN_SECOND_ID) {
+ return EXTRA_EMPTY_SCREEN_ID;
+ } else if (screenId % 2 == 0) {
return screenId + 1;
} else {
return screenId - 1;
@@ -1678,11 +1678,7 @@
}
if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
- PopupContainerWithArrow<Launcher> popupContainer = PopupContainerWithArrow
- .showForIcon((BubbleTextView) child);
- if (popupContainer != null) {
- dragOptions.preDragCondition = popupContainer.createPreDragCondition(true);
- }
+ dragOptions.preDragCondition = ((BubbleTextView) child).startLongPressAction();
}
final DragView dv;
@@ -1732,7 +1728,7 @@
// If it's an external drop (e.g. from All Apps), check if it should be accepted
CellLayout dropTargetLayout = mDropToLayout;
if (d.dragSource != this) {
- // Don't accept the drop if we're not over a screen at time of drop
+ // Don't accept the drop if we're not over a valid drop target at time of drop
if (dropTargetLayout == null) {
return false;
}
@@ -2115,7 +2111,7 @@
final Runnable onCompleteCallback = onCompleteRunnable;
mLauncher.getDragController().animateDragViewToOriginalPosition(
/* onComplete= */ callbackList::executeAllAndDestroy, cell,
- SPRING_LOADED.getTransitionDuration(mLauncher));
+ SPRING_LOADED.getTransitionDuration(mLauncher, true /* isToState */));
mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
onCompleteCallback == null
? null
@@ -2333,17 +2329,6 @@
xy[1] = xy[1] - v.getTop();
}
- boolean isPointInSelfOverHotseat(int x, int y) {
- mTempFXY[0] = x;
- mTempFXY[1] = y;
- mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
- View hotseat = mLauncher.getHotseat();
- return mTempFXY[0] >= hotseat.getLeft()
- && mTempFXY[0] <= hotseat.getRight()
- && mTempFXY[1] >= hotseat.getTop()
- && mTempFXY[1] <= hotseat.getBottom();
- }
-
/**
* Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
* @param layout either hotseat of a page in workspace
@@ -2381,7 +2366,7 @@
final View child = (mDragInfo == null) ? null : mDragInfo.cell;
if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
- if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ if (mDragTargetLayout == null || mLauncher.isHotseatLayout(mDragTargetLayout)) {
mSpringLoadedDragController.cancel();
} else {
mSpringLoadedDragController.setAlarm(mDragTargetLayout);
@@ -2460,52 +2445,25 @@
*/
private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
CellLayout layout = null;
- // Test to see if we are over the hotseat first
- if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
- if (isPointInSelfOverHotseat(d.x, d.y)) {
- layout = mLauncher.getHotseat();
+ if (shouldUseHotseatAsDropLayout(d)) {
+ layout = mLauncher.getHotseat();
+ } else if (!isDragObjectOverSmartSpace(d)) {
+ // If the object is over qsb/smartspace, we don't want to highlight anything.
+
+ // Check neighbour pages
+ layout = checkDragObjectIsOverNeighbourPages(d, centerX);
+
+ if (layout == null) {
+ // Check visible pages
+ IntSet visiblePageIndices = getVisiblePageIndices();
+ for (int visiblePageIndex : visiblePageIndices) {
+ layout = verifyInsidePage(visiblePageIndex, d.x, d.y);
+ if (layout != null) break;
+ }
}
}
- // Note, centerX represents the center of the object that is being dragged, visually. d.x
- // represents the location of the finger within the dragged item.
- float touchX;
- float touchY = d.y;
-
- // Go through the pages and check if the dragged item is inside one of them. This block
- // is responsible for determining whether we need to snap to a different screen.
- int nextPage = getNextPage();
- IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage
- + (isTwoPanelEnabled() ? 2 : 1));
- for (int pageIndex : pageIndexesToVerify) {
- if (layout != null || isPageInTransition()) {
- break;
- }
-
- // When deciding whether to perform a page switch, we need to consider the most extreme
- // X coordinate between the finger location and the center of the object being dragged.
- // This is either the max or the min of the two depending on whether dragging to the
- // left / right, respectively.
- touchX = ((((pageIndex < nextPage) && !mIsRtl) || pageIndex > nextPage && mIsRtl)
- ? Math.min(d.x, centerX) : Math.max(d.x, centerX));
- layout = verifyInsidePage(pageIndex, touchX, touchY);
- }
-
- // If the dragged item isn't located in one of the pages above, the icon will stay on the
- // current screen. For two panel pick the closest panel on the current screen,
- // on one panel just choose the current page.
- if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
- if (isTwoPanelEnabled()) {
- // When determining which panel to use within a single screen, we always use
- // the centroid of the object rather than the finger.
- touchX = centerX;
- nextPage = getScreenCenter(getScrollX()) > touchX
- ? (mIsRtl ? nextPage + 1 : nextPage) // left side
- : (mIsRtl ? nextPage : nextPage + 1); // right side
- }
- layout = (CellLayout) getChildAt(nextPage);
- }
-
+ // Update the current drop layout if the target changed
if (layout != mDragTargetLayout) {
setCurrentDropLayout(layout);
setCurrentDragOverlappingLayout(layout);
@@ -2514,6 +2472,69 @@
return false;
}
+ private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
+ if (mLauncher.getHotseat() == null
+ || mLauncher.getHotseat().getShortcutsAndWidgets() == null
+ || isDragWidget(dragObject)) {
+ return false;
+ }
+ View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
+ getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
+ return mTempRect.contains(dragObject.x, dragObject.y);
+ }
+
+ private boolean isDragObjectOverSmartSpace(DragObject dragObject) {
+ if (mQsb == null) {
+ return false;
+ }
+ getViewBoundsRelativeToWorkspace(mQsb, mTempRect);
+ return mTempRect.contains(dragObject.x, dragObject.y);
+ }
+
+ private CellLayout checkDragObjectIsOverNeighbourPages(DragObject d, float centerX) {
+ if (isPageInTransition()) {
+ return null;
+ }
+
+ // Check the workspace pages whether the object is over any of them
+
+ // Note, centerX represents the center of the object that is being dragged, visually.
+ // d.x represents the location of the finger within the dragged item.
+ float touchX;
+ float touchY = d.y;
+
+ // Go through the pages and check if the dragged item is inside one of them. This block
+ // is responsible for determining whether we need to snap to a different screen.
+ int nextPage = getNextPage();
+ IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1,
+ nextPage + (isTwoPanelEnabled() ? 2 : 1));
+
+ for (int pageIndex : pageIndexesToVerify) {
+ // When deciding whether to perform a page switch, we need to consider the most
+ // extreme X coordinate between the finger location and the center of the object
+ // being dragged. This is either the max or the min of the two depending on whether
+ // dragging to the left / right, respectively.
+ touchX = (((pageIndex < nextPage) && !mIsRtl) || (pageIndex > nextPage && mIsRtl))
+ ? Math.min(d.x, centerX) : Math.max(d.x, centerX);
+ CellLayout layout = verifyInsidePage(pageIndex, touchX, touchY);
+ if (layout != null) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the given view's bounds relative to Workspace
+ */
+ private void getViewBoundsRelativeToWorkspace(View view, Rect outRect) {
+ mLauncher.getDragLayer()
+ .getDescendantRectRelativeToSelf(view, mTempRect);
+ // map draglayer relative bounds to workspace
+ mLauncher.getDragLayer().mapRectInSelfToDescendant(this, mTempRect);
+ outRect.set(mTempRect);
+ }
+
/**
* Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
*/
@@ -2750,9 +2771,9 @@
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
- if (info instanceof AppInfo) {
+ if (info instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy
- info = ((AppInfo) info).makeWorkspaceItem();
+ info = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);
d.dragInfo = info;
}
if (info instanceof WorkspaceItemInfo
@@ -2761,11 +2782,6 @@
info = new WorkspaceItemInfo((WorkspaceItemInfo) info);
d.dragInfo = info;
}
- if (info instanceof SearchActionItemInfo) {
- info = ((SearchActionItemInfo) info).createWorkspaceItem(
- mLauncher.getModel());
- d.dragInfo = info;
- }
view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -3207,7 +3223,7 @@
* as a part of an update, this is called to ensure that other widgets and application
* shortcuts are not removed.
*/
- public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
+ public void removeItemsByMatcher(final Predicate<ItemInfo> matcher) {
for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
// Iterate in reverse order as we are removing items
@@ -3215,7 +3231,7 @@
View child = container.getChildAt(i);
ItemInfo info = (ItemInfo) child.getTag();
- if (matcher.matchesInfo(info)) {
+ if (matcher.test(info)) {
layout.removeViewInLayout(child);
if (child instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) child);
@@ -3223,7 +3239,7 @@
} else if (child instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
- .filter(matcher::matchesInfo)
+ .filter(matcher)
.collect(Collectors.toList());
if (!matches.isEmpty()) {
folderInfo.removeAll(matches, false);
@@ -3297,9 +3313,12 @@
}
}
- public void removeAbandonedPromise(String packageName, UserHandle user) {
- ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(
- Collections.singleton(packageName), user);
+ /**
+ * Remove workspace icons & widget information related to items in matcher.
+ *
+ * @param matcher the matcher generated by the caller.
+ */
+ public void persistRemoveItemsByMatcher(Predicate<ItemInfo> matcher) {
mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
removeItemsByMatcher(matcher);
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 84b95ec..a991c2f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -25,6 +25,7 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherState.FLAG_HAS_SYS_UI_SCRIM;
+import static com.android.launcher3.LauncherState.FLAG_HOTSEAT_INACCESSIBLE;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -35,6 +36,7 @@
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
@@ -116,8 +118,6 @@
}
int elements = state.getVisibleElements(mLauncher);
- Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
- pageAlphaProvider.interpolator);
Hotseat hotseat = mWorkspace.getHotseat();
Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
LauncherState fromState = mLauncher.getStateManager().getState();
@@ -145,11 +145,21 @@
hotseatScaleInterpolator);
}
- float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
- propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
+ Interpolator workspaceFadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
+ pageAlphaProvider.interpolator);
float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- workspacePageIndicatorAlpha, fadeInterpolator);
+ workspacePageIndicatorAlpha, workspaceFadeInterpolator);
+ Interpolator hotseatFadeInterpolator = config.getInterpolator(ANIM_HOTSEAT_FADE,
+ workspaceFadeInterpolator);
+ float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+ propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, hotseatFadeInterpolator);
+
+ // Update the accessibility flags for hotseat based on launcher state.
+ hotseat.setImportantForAccessibility(
+ state.hasFlag(FLAG_HOTSEAT_INACCESSIBLE)
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
Interpolator translationInterpolator =
config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
index 14b2431..19d0421 100644
--- a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.dragndrop.DragController;
@@ -31,9 +32,9 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BubbleTextHolder;
import java.util.ArrayList;
import java.util.List;
@@ -72,7 +73,7 @@
getSupportedActions(host, item, actions);
actions.forEach(la -> info.addAction(la.accessibilityAction));
- if (!itemSupportsLongClick(host, item)) {
+ if (!itemSupportsLongClick(host)) {
info.setLongClickable(false);
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
}
@@ -84,8 +85,15 @@
*/
protected abstract void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out);
- private boolean itemSupportsLongClick(View host, ItemInfo info) {
- return PopupContainerWithArrow.canShow(host, info);
+ private boolean itemSupportsLongClick(View host) {
+ if (host instanceof BubbleTextView) {
+ return ((BubbleTextView) host).canShowLongPressPopup();
+ } else if (host instanceof BubbleTextHolder) {
+ BubbleTextHolder holder = (BubbleTextHolder) host;
+ return holder.getBubbleText() != null && holder.getBubbleText().canShowLongPressPopup();
+ } else {
+ return false;
+ }
}
protected boolean itemSupportsAccessibleDrag(ItemInfo item) {
@@ -113,7 +121,6 @@
@Thunk
protected void announceConfirmation(String confirmation) {
mContext.getDragLayer().announceForAccessibility(confirmation);
-
}
public boolean isInAccessibleDrag() {
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 462daf5..44d57d7 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -24,13 +24,14 @@
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.keyboard.KeyboardDragAndDropView;
-import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.ArrowPopup;
@@ -40,6 +41,7 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.BubbleTextHolder;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.OptionsPopupView.OptionItem;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -118,7 +120,7 @@
}
}
- if ((item instanceof AppInfo) || (item instanceof WorkspaceItemInfo)
+ if ((item instanceof WorkspaceItemFactory) || (item instanceof WorkspaceItemInfo)
|| (item instanceof PendingAddItemInfo)) {
out.add(mActions.get(ADD_TO_WORKSPACE));
}
@@ -143,13 +145,18 @@
protected boolean performAction(final View host, final ItemInfo item, int action,
boolean fromKeyboard) {
if (action == ACTION_LONG_CLICK) {
- if (PopupContainerWithArrow.canShow(host, item)) {
- // Long press should be consumed for workspace items, and it should invoke the
- // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
- // standard long press path does.
- PopupContainerWithArrow.showForIcon((BubbleTextView) host);
- return true;
+ PreDragCondition dragCondition = null;
+ // Long press should be consumed for workspace items, and it should invoke the
+ // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
+ // standard long press path does.
+ if (host instanceof BubbleTextView) {
+ dragCondition = ((BubbleTextView) host).startLongPressAction();
+ } else if (host instanceof BubbleTextHolder) {
+ BubbleTextHolder holder = (BubbleTextHolder) host;
+ dragCondition = holder.getBubbleText() == null ? null
+ : holder.getBubbleText().startLongPressAction();
}
+ return dragCondition != null;
} else if (action == MOVE) {
return beginAccessibleDrag(host, item, fromKeyboard);
} else if (action == ADD_TO_WORKSPACE) {
@@ -166,7 +173,10 @@
popup.setOnCloseCallback(host::requestFocus);
return true;
} else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
- return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
+ BubbleTextView btv = host instanceof BubbleTextView ? (BubbleTextView) host
+ : (host instanceof BubbleTextHolder
+ ? ((BubbleTextHolder) host).getBubbleText() : null);
+ return btv != null && PopupContainerWithArrow.showForIcon(btv) != null;
} else {
for (ButtonDropTarget dropTarget : mContext.getDropTargetBar().getDropTargets()) {
if (dropTarget.supportsAccessibilityDrop(item, host)
@@ -313,7 +323,7 @@
* Find empty space on the workspace and returns the screenId.
*/
protected int findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
- Workspace workspace = mContext.getWorkspace();
+ Workspace<?> workspace = mContext.getWorkspace();
IntArray workspaceScreens = workspace.getScreenOrder();
int screenId;
@@ -367,8 +377,8 @@
return false;
}
mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
- if (item instanceof AppInfo) {
- WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
+ if (item instanceof WorkspaceItemFactory) {
+ WorkspaceItemInfo info = ((WorkspaceItemFactory) item).makeWorkspaceItem(mContext);
mContext.getModelWriter().addItemToDatabase(info,
LauncherSettings.Favorites.CONTAINER_DESKTOP,
screenId, coordinates[0], coordinates[1]);
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index e279f59..47f2dd5 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -29,11 +29,13 @@
import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.AppLauncher;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -95,11 +97,15 @@
mHeader.reset(false);
}
- /** Invoke when the search results change. */
- public void onSearchResultsChanged() {
- for (int i = 0; i < mAH.size(); i++) {
- if (mAH.get(i).mRecyclerView != null) {
- mAH.get(i).mRecyclerView.onSearchResultsChanged();
+ /**
+ * Sets results list for search
+ */
+ public void setSearchResults(ArrayList<AdapterItem> results) {
+ if (getApps().setSearchResults(results)) {
+ for (int i = 0; i < mAH.size(); i++) {
+ if (mAH.get(i).mRecyclerView != null) {
+ mAH.get(i).mRecyclerView.onSearchResultsChanged();
+ }
}
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index f97eb28..7067fa2 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -37,10 +37,10 @@
* Smooth scrolls the recycler view to the given section.
*/
public void smoothScrollToSection(FastScrollSectionInfo info) {
- if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+ if (mTargetFastScrollPosition == info.position) {
return;
}
- mTargetFastScrollPosition = info.fastScrollToItem.position;
+ mTargetFastScrollPosition = info.position;
mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 58df50c..33d2f2b 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -118,7 +118,7 @@
List<AdapterItem> items = mApps.getAdapterItems();
adapterPosition = Math.max(adapterPosition, items.size() - 1);
int extraRows = 0;
- for (int i = 0; i <= adapterPosition; i++) {
+ for (int i = 0; i <= adapterPosition && i < items.size(); i++) {
if (!isViewType(items.get(i).viewType, VIEW_TYPE_MASK_ICON)) {
extraRows++;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7dbe711..096e2c8 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -71,6 +71,26 @@
public void onChanged() {
mCachedScrollPositions.clear();
}
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ onChanged();
+ }
};
// The empty-search result background
@@ -208,6 +228,8 @@
requestFocus();
mgr.logger().sendToInteractionJankMonitor(
LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+ hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
+ getApplicationWindowToken());
break;
case SCROLL_STATE_IDLE:
mgr.logger().sendToInteractionJankMonitor(
@@ -223,8 +245,6 @@
&& mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
mEmptySearchBackground.setHotspot(e.getX(), e.getY());
}
- hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
- getApplicationWindowToken());
return result;
}
@@ -241,17 +261,14 @@
// Find the fastscroll section that maps to this touch fraction
List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
mApps.getFastScrollerSections();
- AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
- for (int i = 1; i < fastScrollSections.size(); i++) {
- AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
- if (info.touchFraction > touchFraction) {
- break;
- }
- lastInfo = info;
+ int count = fastScrollSections.size();
+ if (count == 0) {
+ return "";
}
-
- mFastScrollHelper.smoothScrollToSection(lastInfo);
- return lastInfo.sectionName;
+ int index = Utilities.boundToRange((int) (touchFraction * count), 0, count - 1);
+ AlphabeticalAppsList.FastScrollSectionInfo section = fastScrollSections.get(index);
+ mFastScrollHelper.smoothScrollToSection(section);
+ return section.sectionName;
}
@Override
@@ -272,12 +289,6 @@
}
@Override
- protected float getBottomFadingEdgeStrength() {
- // No bottom fading edge.
- return 0;
- }
-
- @Override
protected boolean isPaddingOffsetRequired() {
return true;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index cdc313f..3600bd2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -44,6 +44,9 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiAdditivePropertyFactory;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.views.ScrimView;
/**
@@ -75,6 +78,57 @@
}
};
+ public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_TRANSLATION =
+ new FloatProperty<AllAppsTransitionController>("allAppsPullBackTranslation") {
+
+ @Override
+ public Float get(AllAppsTransitionController controller) {
+ if (controller.mIsTablet) {
+ return controller.mAppsView.getRecyclerViewContainer().getTranslationY();
+ } else {
+ return controller.getAppsViewPullbackTranslationY().get(
+ controller.mAppsView);
+ }
+ }
+
+ @Override
+ public void setValue(AllAppsTransitionController controller, float translation) {
+ if (controller.mIsTablet) {
+ controller.mAppsView.getRecyclerViewContainer().setTranslationY(
+ translation);
+ } else {
+ controller.getAppsViewPullbackTranslationY().set(controller.mAppsView,
+ translation);
+ }
+ }
+ };
+
+ public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_ALPHA =
+ new FloatProperty<AllAppsTransitionController>("allAppsPullBackAlpha") {
+
+ @Override
+ public Float get(AllAppsTransitionController controller) {
+ if (controller.mIsTablet) {
+ return controller.mAppsView.getRecyclerViewContainer().getAlpha();
+ } else {
+ return controller.getAppsViewPullbackAlpha().getValue();
+ }
+ }
+
+ @Override
+ public void setValue(AllAppsTransitionController controller, float alpha) {
+ if (controller.mIsTablet) {
+ controller.mAppsView.getRecyclerViewContainer().setAlpha(alpha);
+ } else {
+ controller.getAppsViewPullbackAlpha().setValue(alpha);
+ }
+ }
+ };
+
+ private static final int INDEX_APPS_VIEW_PROGRESS = 0;
+ private static final int INDEX_APPS_VIEW_PULLBACK = 1;
+ private static final int APPS_VIEW_INDEX_COUNT = 2;
+
private ActivityAllAppsContainerView<Launcher> mAppsView;
private final Launcher mLauncher;
@@ -91,13 +145,20 @@
private ScrimView mScrimView;
+ private final MultiAdditivePropertyFactory<View>
+ mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>(
+ "appsViewTranslationY", View.TRANSLATION_Y);
+ private MultiValueAlpha mAppsViewAlpha;
+
+ private boolean mIsTablet;
+
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
DeviceProfile dp = mLauncher.getDeviceProfile();
setShiftRange(dp.allAppsShiftRange);
mProgress = 1f;
-
mIsVerticalLayout = dp.isVerticalBarLayout();
+ mIsTablet = dp.isTablet;
mLauncher.addOnDeviceProfileChangeListener(this);
}
@@ -114,6 +175,8 @@
mLauncher.getHotseat().setTranslationY(0);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
}
+
+ mIsTablet = dp.isTablet;
}
/**
@@ -126,13 +189,30 @@
*/
public void setProgress(float progress) {
mProgress = progress;
- mAppsView.setTranslationY(mProgress * mShiftRange);
+ getAppsViewProgressTranslationY().set(mAppsView, mProgress * mShiftRange);
+ mLauncher.onAllAppsTransition(1 - progress);
}
public float getProgress() {
return mProgress;
}
+ private FloatProperty<View> getAppsViewProgressTranslationY() {
+ return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PROGRESS);
+ }
+
+ private FloatProperty<View> getAppsViewPullbackTranslationY() {
+ return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PULLBACK);
+ }
+
+ private MultiValueAlpha.AlphaProperty getAppsViewProgressAlpha() {
+ return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PROGRESS);
+ }
+
+ private MultiValueAlpha.AlphaProperty getAppsViewPullbackAlpha() {
+ return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PULLBACK);
+ }
+
/**
* Sets the vertical transition progress to {@param state} and updates all the dependent UI
* accordingly.
@@ -151,6 +231,15 @@
@Override
public void setStateWithAnimation(LauncherState toState,
StateAnimationConfig config, PendingAnimation builder) {
+ if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) {
+ UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken());
+ builder.addEndListener(success -> {
+ // Reset pull back progress and alpha after switching states.
+ ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
+ ALL_APPS_PULL_BACK_ALPHA.set(this, 1f);
+ });
+ }
+
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
setAlphas(toState, config, builder);
@@ -166,8 +255,6 @@
anim.setInterpolator(verticalProgressInterpolator);
anim.addListener(getProgressAnimatorListener());
builder.add(anim);
- // Use ANIM_VERTICAL_PROGRESS's interpolator to determine state transition threshold.
- builder.setInterpolator(verticalProgressInterpolator);
setAlphas(toState, config, builder);
@@ -189,7 +276,8 @@
boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
- setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setFloat(getAppsViewProgressAlpha(), MultiValueAlpha.VALUE,
+ hasAllAppsContent ? 1 : 0, allAppsFade);
boolean shouldProtectHeader =
ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS;
@@ -212,6 +300,8 @@
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
mAppsView.setScrimView(scrimView);
+ mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
+ mAppsViewAlpha.setUpdateVisibility(true);
}
/**
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 7687fea..4ccfd39 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,23 +15,29 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_SEARCH_MARKET;
import android.content.Context;
+import androidx.recyclerview.widget.DiffUtil;
+
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.LabelComparator;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* The alphabetically sorted list of applications.
@@ -43,10 +49,6 @@
public static final String TAG = "AlphabeticalAppsList";
- private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
- private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
-
- private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
private final WorkAdapterProvider mWorkAdapterProvider;
/**
@@ -55,14 +57,13 @@
*/
public static class FastScrollSectionInfo {
// The section name
- public String sectionName;
- // The AdapterItem to scroll to for this section
- public AdapterItem fastScrollToItem;
- // The touch fraction that should map to this fast scroll section info
- public float touchFraction;
+ public final String sectionName;
+ // The item position
+ public final int position;
- public FastScrollSectionInfo(String sectionName) {
+ public FastScrollSectionInfo(String sectionName, int position) {
this.sectionName = sectionName;
+ this.position = position;
}
}
@@ -86,7 +87,7 @@
private AppInfoComparator mAppNameComparator;
private final int mNumAppsPerRowAllApps;
private int mNumAppRowsInAdapter;
- private ItemInfoMatcher mItemFilter;
+ private Predicate<ItemInfo> mItemFilter;
public AlphabeticalAppsList(Context context, AllAppsStore appsStore,
WorkAdapterProvider adapterProvider) {
@@ -98,7 +99,7 @@
mAllAppsStore.addUpdateListener(this);
}
- public void updateItemFilter(ItemInfoMatcher itemFilter) {
+ public void updateItemFilter(Predicate<ItemInfo> itemFilter) {
this.mItemFilter = itemFilter;
onAppsUpdated();
}
@@ -111,13 +112,6 @@
}
/**
- * Returns all the apps.
- */
- public List<AppInfo> getApps() {
- return mApps;
- }
-
- /**
* Returns fast scroller sections of all the current filtered applications.
*/
public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -196,27 +190,6 @@
return true;
}
- public boolean appendSearchResults(ArrayList<AdapterItem> results) {
- if (hasFilter() && results != null && results.size() > 0) {
- updateSearchAdapterItems(results, mSearchResults.size());
- refreshRecyclerView();
- return true;
- }
- return false;
- }
-
- void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
- for (int i = 0; i < list.size(); i++) {
- AdapterItem adapterItem = list.get(i);
- adapterItem.position = offset + i;
- mAdapterItems.add(adapterItem);
-
- if (adapterItem.isCountedForAccessibility()) {
- mAccessibilityResultsCount++;
- }
- }
- }
-
/**
* Updates internals when the set of apps are updated.
*/
@@ -225,13 +198,11 @@
// Sort the list of apps
mApps.clear();
- for (AppInfo app : mAllAppsStore.getApps()) {
- if (mItemFilter == null || mItemFilter.matches(app, null) || hasFilter()) {
- mApps.add(app);
- }
+ Stream<AppInfo> appSteam = Stream.of(mAllAppsStore.getApps());
+ if (!hasFilter() && mItemFilter != null) {
+ appSteam = appSteam.filter(mItemFilter);
}
-
- Collections.sort(mApps, mAppNameComparator);
+ appSteam = appSteam.sorted(mAppNameComparator);
// As a special case for some languages (currently only Simplified Chinese), we may need to
// coalesce sections
@@ -240,27 +211,16 @@
if (localeRequiresSectionSorting) {
// Compute the section headers. We use a TreeMap with the section name comparator to
// ensure that the sections are ordered when we iterate over it later
- TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
- for (AppInfo info : mApps) {
- // Add the section to the cache
- String sectionName = info.sectionName;
-
- // Add it to the mapping
- ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
- if (sectionApps == null) {
- sectionApps = new ArrayList<>();
- sectionMap.put(sectionName, sectionApps);
- }
- sectionApps.add(info);
- }
-
- // Add each of the section apps to the list in order
- mApps.clear();
- for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
- mApps.addAll(entry.getValue());
- }
+ appSteam = appSteam.collect(Collectors.groupingBy(
+ info -> info.sectionName,
+ () -> new TreeMap<>(new LabelComparator()),
+ Collectors.toCollection(ArrayList::new)))
+ .values()
+ .stream()
+ .flatMap(ArrayList::stream);
}
+ appSteam.forEachOrdered(mApps::add);
// Recompose the set of adapter items from the current set of apps
if (mSearchResults.isEmpty()) {
updateAdapterItems();
@@ -272,70 +232,49 @@
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
public void updateAdapterItems() {
- refillAdapterItems();
- refreshRecyclerView();
- }
-
- private void refreshRecyclerView() {
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
-
- private void refillAdapterItems() {
- String lastSectionName = null;
- FastScrollSectionInfo lastFastScrollerSectionInfo = null;
- int position = 0;
- int appIndex = 0;
-
+ List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
// Prepare to update the list of sections, filtered apps, etc.
- mAccessibilityResultsCount = 0;
mFastScrollerSections.clear();
mAdapterItems.clear();
+ mAccessibilityResultsCount = 0;
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
-
if (!hasFilter()) {
- mAccessibilityResultsCount = mApps.size();
+ int position = 0;
if (mWorkAdapterProvider != null) {
position += mWorkAdapterProvider.addWorkItems(mAdapterItems);
if (!mWorkAdapterProvider.shouldShowWorkApps()) {
return;
}
}
+ String lastSectionName = null;
for (AppInfo info : mApps) {
- String sectionName = info.sectionName;
+ mAdapterItems.add(AdapterItem.asApp(info));
+ String sectionName = info.sectionName;
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
- lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
- mFastScrollerSections.add(lastFastScrollerSectionInfo);
+ mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position));
}
-
- // Create an app item
- AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info,
- appIndex++);
- if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
- lastFastScrollerSectionInfo.fastScrollToItem = appItem;
- }
-
- mAdapterItems.add(appItem);
+ position++;
}
} else {
- updateSearchAdapterItems(mSearchResults, 0);
+ mAdapterItems.addAll(mSearchResults);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
// Append the search market item
if (hasNoFilteredResults()) {
- mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_EMPTY_SEARCH));
} else {
- mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_ALL_APPS_DIVIDER));
}
- mAdapterItems.add(AdapterItem.asMarketSearch(position++));
-
+ mAdapterItems.add(new AdapterItem(VIEW_TYPE_SEARCH_MARKET));
}
}
+ mAccessibilityResultsCount = (int) mAdapterItems.stream()
+ .filter(AdapterItem::isCountedForAccessibility).count();
+
if (mNumAppsPerRowAllApps != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
// would have to shift the values again)
@@ -358,37 +297,43 @@
}
}
mNumAppRowsInAdapter = rowIndex + 1;
+ }
- // Pre-calculate all the fast scroller fractions
- switch (mFastScrollDistributionMode) {
- case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
- float rowFraction = 1f / mNumAppRowsInAdapter;
- for (FastScrollSectionInfo info : mFastScrollerSections) {
- AdapterItem item = info.fastScrollToItem;
- if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
- info.touchFraction = 0f;
- continue;
- }
-
- float subRowFraction =
- item.rowAppIndex * (rowFraction / mNumAppsPerRowAllApps);
- info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
- }
- break;
- case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
- float perSectionTouchFraction = 1f / mFastScrollerSections.size();
- float cumulativeTouchFraction = 0f;
- for (FastScrollSectionInfo info : mFastScrollerSections) {
- AdapterItem item = info.fastScrollToItem;
- if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
- info.touchFraction = 0f;
- continue;
- }
- info.touchFraction = cumulativeTouchFraction;
- cumulativeTouchFraction += perSectionTouchFraction;
- }
- break;
- }
+ if (mAdapter != null) {
+ DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
+ .dispatchUpdatesTo(mAdapter);
}
}
+
+ private static class MyDiffCallback extends DiffUtil.Callback {
+
+ private final List<AdapterItem> mOldList;
+ private final List<AdapterItem> mNewList;
+
+ MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
+ mOldList = oldList;
+ mNewList = newList;
+ }
+
+ @Override
+ public int getOldListSize() {
+ return mOldList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mNewList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 976284d..c7c4607 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -36,7 +36,6 @@
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.views.ActivityContext;
import java.util.Arrays;
@@ -89,72 +88,47 @@
*/
public static class AdapterItem {
/** Common properties */
- // The index of this adapter item in the list
- public int position;
// The type of this item
- public int viewType;
+ public final int viewType;
- // The section name of this item. Note that there can be multiple items with different
- // sectionNames in the same section
- public String sectionName = null;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated ItemInfoWithIcon for the item
- public ItemInfoWithIcon itemInfo = null;
- // The index of this app not including sections
- public int appIndex = -1;
- // Search section associated to result
- public DecorationInfo decorationInfo = null;
+ public AppInfo itemInfo = null;
+
+ public AdapterItem(int viewType) {
+ this.viewType = viewType;
+ }
/**
* Factory method for AppIcon AdapterItem
*/
- public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
- int appIndex) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ICON;
- item.position = pos;
- item.sectionName = sectionName;
+ public static AdapterItem asApp(AppInfo appInfo) {
+ AdapterItem item = new AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = appInfo;
- item.appIndex = appIndex;
- return item;
- }
-
- /**
- * Factory method for empty search results view
- */
- public static AdapterItem asEmptySearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_EMPTY_SEARCH;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a dividerView in AllAppsSearch
- */
- public static AdapterItem asAllAppsDivider(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
- item.position = pos;
- return item;
- }
-
- /**
- * Factory method for a market search button
- */
- public static AdapterItem asMarketSearch(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = VIEW_TYPE_SEARCH_MARKET;
- item.position = pos;
return item;
}
protected boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
}
+
+ /**
+ * Returns true if the items represent the same object
+ */
+ public boolean isSameAs(AdapterItem other) {
+ return (other.viewType != viewType) && (other.getClass() == getClass());
+ }
+
+ /**
+ * This is called only if {@link #isSameAs} returns true to check if the contents are same
+ * as well. Returning true will prevent redrawing of thee item.
+ */
+ public boolean isContentSame(AdapterItem other) {
+ return itemInfo == null && other.itemInfo == null;
+ }
}
protected final T mActivityContext;
@@ -267,11 +241,7 @@
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
- if (adapterItem.itemInfo instanceof AppInfo) {
- icon.applyFromApplicationInfo((AppInfo) adapterItem.itemInfo);
- } else {
- icon.applyFromItemInfoWithIcon(adapterItem.itemInfo);
- }
+ icon.applyFromApplicationInfo(adapterItem.itemInfo);
break;
case VIEW_TYPE_EMPTY_SEARCH:
TextView emptyViewText = (TextView) holder.itemView;
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index f913aa9..fc52797 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
+import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -58,7 +59,7 @@
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.StringCache;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -69,6 +70,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
/**
* Base all apps view container.
@@ -91,7 +94,7 @@
/** Context of an activity or window that is inflating this container. */
protected final T mActivityContext;
protected final List<AdapterHolder> mAH;
- protected final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(
+ protected final Predicate<ItemInfo> mPersonalMatcher = ItemInfoMatcher.ofUser(
Process.myUserHandle());
private final SearchAdapterProvider<?> mMainAdapterProvider;
private final AllAppsStore mAllAppsStore = new AllAppsStore();
@@ -140,7 +143,7 @@
mWorkManager = new WorkProfileManager(
mActivityContext.getSystemService(UserManager.class),
this,
- Utilities.getPrefs(mActivityContext));
+ Utilities.getPrefs(mActivityContext), mActivityContext.getDeviceProfile());
mAH = Arrays.asList(null, null);
mAH.set(AdapterHolder.MAIN, new AdapterHolder(false /* isWork */));
mAH.set(AdapterHolder.WORK, new AdapterHolder(true /* isWork */));
@@ -229,17 +232,10 @@
}
private void onAppsUpdated() {
- boolean hasWorkApps = false;
- for (AppInfo app : mAllAppsStore.getApps()) {
- if (mWorkManager.getMatcher().matches(app, null)) {
- hasWorkApps = true;
- break;
- }
- }
- mHasWorkApps = hasWorkApps;
+ mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
if (!mAH.get(AdapterHolder.MAIN).mAppsList.hasFilter()) {
rebindAdapters();
- if (hasWorkApps) {
+ if (mHasWorkApps) {
mWorkManager.reset();
}
}
@@ -330,7 +326,7 @@
}
protected boolean isPersonalTab() {
- return mViewPager.getNextPage() == 0;
+ return mViewPager == null || mViewPager.getNextPage() == 0;
}
/**
@@ -392,34 +388,34 @@
mInsets.set(insets);
DeviceProfile grid = mActivityContext.getDeviceProfile();
- for (int i = 0; i < mAH.size(); i++) {
- mAH.get(i).mPadding.bottom = insets.bottom;
- mAH.get(i).mPadding.left = mAH.get(i).mPadding.right = grid.allAppsLeftRightPadding;
- mAH.get(i).applyPadding();
- }
+ applyAdapterPaddings(grid);
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
- int leftRightMargin = grid.allAppsLeftRightMargin;
- mlp.leftMargin = insets.left + leftRightMargin;
- mlp.rightMargin = insets.right + leftRightMargin;
+ mlp.leftMargin = insets.left;
+ mlp.rightMargin = insets.right;
setLayoutParams(mlp);
if (grid.isVerticalBarLayout()) {
setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
} else {
- setPadding(0, grid.allAppsTopPadding, 0, 0);
+ setPadding(grid.allAppsLeftRightMargin, grid.allAppsTopPadding,
+ grid.allAppsLeftRightMargin, 0);
}
InsettableFrameLayout.dispatchInsets(this, insets);
}
+ /**
+ * Returns a padding in case a scrim is shown on the bottom of the view and a padding is needed.
+ */
+ protected int getNavBarScrimHeight(WindowInsets insets) {
+ return 0;
+ }
+
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
- if (Utilities.ATLEAST_Q) {
- mNavBarScrimHeight = insets.getTappableElementInsets().bottom;
- } else {
- mNavBarScrimHeight = insets.getStableInsetBottom();
- }
+ mNavBarScrimHeight = getNavBarScrimHeight(insets);
+ applyAdapterPaddings(mActivityContext.getDeviceProfile());
return super.dispatchApplyWindowInsets(insets);
}
@@ -442,8 +438,12 @@
if (showTabs == mUsingTabs && !force) {
return;
}
+
+ // replaceRVcontainer() needs to use both mUsingTabs value to remove the old view AND
+ // showTabs value to create new view. Hence the mUsingTabs new value assignment MUST happen
+ // after this call.
+ replaceRVContainer(showTabs);
mUsingTabs = showTabs;
- replaceRVContainer(mUsingTabs);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
@@ -459,6 +459,8 @@
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
}
+ hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
+ getApplicationWindowToken());
});
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> {
@@ -466,6 +468,8 @@
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
}
+ hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
+ getApplicationWindowToken());
});
setDeviceManagementResources();
onActivePageChanged(mViewPager.getNextPage());
@@ -479,6 +483,15 @@
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
}
+ private void applyAdapterPaddings(DeviceProfile grid) {
+ int bottomPadding = Math.max(mInsets.bottom, mNavBarScrimHeight);
+ for (int i = 0; i < mAH.size(); i++) {
+ mAH.get(i).mPadding.bottom = bottomPadding;
+ mAH.get(i).mPadding.left = mAH.get(i).mPadding.right = grid.allAppsLeftRightPadding;
+ mAH.get(i).applyPadding();
+ }
+ }
+
private void setDeviceManagementResources() {
if (mActivityContext.getStringCache() != null) {
Button personalTab = findViewById(R.id.tab_personal);
@@ -597,13 +610,6 @@
}
}
- /** @see View#setVerticalFadingEdgeEnabled(boolean). */
- public void setRecyclerViewVerticalFadingEdgeEnabled(boolean enabled) {
- for (int i = 0; i < mAH.size(); i++) {
- mAH.get(i).applyVerticalFadingEdgeEnabled(enabled);
- }
- }
-
public boolean isHeaderVisible() {
return mHeader != null && mHeader.getVisibility() == View.VISIBLE;
}
@@ -714,7 +720,6 @@
final AlphabeticalAppsList<T> mAppsList;
final Rect mPadding = new Rect();
AllAppsRecyclerView mRecyclerView;
- boolean mVerticalFadingEdge;
AdapterHolder(boolean isWork) {
mIsWork = isWork;
@@ -731,7 +736,7 @@
mLayoutManager = adapter.getLayoutManager();
}
- void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+ void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
mAppsList.updateItemFilter(matcher);
mRecyclerView = (AllAppsRecyclerView) rv;
mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
@@ -745,7 +750,6 @@
FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mRecyclerView);
mRecyclerView.addItemDecoration(focusedItemDecorator);
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
- applyVerticalFadingEdgeEnabled(mVerticalFadingEdge);
applyPadding();
}
@@ -759,12 +763,6 @@
mPadding.bottom + bottomOffset);
}
}
-
- private void applyVerticalFadingEdgeEnabled(boolean enabled) {
- mVerticalFadingEdge = enabled;
- mAH.get(AdapterHolder.MAIN).mRecyclerView.setVerticalFadingEdgeEnabled(!mUsingTabs
- && mVerticalFadingEdge);
- }
}
/**
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 8601819..20f5e74 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -18,9 +18,11 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
/**
* AllAppsContainerView with launcher specific callbacks
@@ -58,4 +60,13 @@
}
return super.onTouchEvent(ev);
}
+
+ @Override
+ protected int getNavBarScrimHeight(WindowInsets insets) {
+ if (Utilities.ATLEAST_Q) {
+ return insets.getTappableElementInsets().bottom;
+ } else {
+ return insets.getStableInsetBottom();
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/WorkAdapterProvider.java b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
index ce44958..76d08c8 100644
--- a/src/com/android/launcher3/allapps/WorkAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
@@ -19,10 +19,10 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.TextView;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
@@ -107,13 +107,9 @@
public int addWorkItems(ArrayList<AllAppsGridAdapter.AdapterItem> adapterItems) {
if (mState == WorkProfileManager.STATE_DISABLED) {
//add disabled card here.
- AllAppsGridAdapter.AdapterItem disabledCard = new AllAppsGridAdapter.AdapterItem();
- disabledCard.viewType = VIEW_TYPE_WORK_DISABLED_CARD;
- adapterItems.add(disabledCard);
+ adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
} else if (mState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
- AllAppsGridAdapter.AdapterItem eduCard = new AllAppsGridAdapter.AdapterItem();
- eduCard.viewType = VIEW_TYPE_WORK_EDU_CARD;
- adapterItems.add(eduCard);
+ adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
}
return adapterItems.size();
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index bcb0d14..dc9f18c 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -32,13 +32,15 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.function.Predicate;
/**
* Companion class for {@link BaseAllAppsContainerView} to manage work tab and personal tab
@@ -69,18 +71,20 @@
private final BaseAllAppsContainerView<?> mAllApps;
private final WorkAdapterProvider mAdapterProvider;
- private final ItemInfoMatcher mMatcher;
+ private final Predicate<ItemInfo> mMatcher;
private WorkModeSwitch mWorkModeSwitch;
+ private final DeviceProfile mDeviceProfile;
@WorkProfileState
private int mCurrentState;
public WorkProfileManager(UserManager userManager, BaseAllAppsContainerView<?> allApps,
- SharedPreferences preferences) {
+ SharedPreferences preferences, DeviceProfile deviceProfile) {
mUserManager = userManager;
mAllApps = allApps;
+ mDeviceProfile = deviceProfile;
mAdapterProvider = new WorkAdapterProvider(allApps.mActivityContext, preferences);
mMatcher = mAllApps.mPersonalMatcher.negate();
}
@@ -140,15 +144,24 @@
mWorkModeSwitch = (WorkModeSwitch) mAllApps.getLayoutInflater().inflate(
R.layout.work_mode_fab, mAllApps, false);
}
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mWorkModeSwitch.getLayoutParams();
int workFabMarginBottom =
- mWorkModeSwitch.getResources().getDimensionPixelSize(R.dimen.work_fab_margin);
+ mWorkModeSwitch.getResources().getDimensionPixelSize(
+ R.dimen.work_fab_margin_bottom);
if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
workFabMarginBottom <<= 1; // Double margin to add space above search bar.
workFabMarginBottom +=
mWorkModeSwitch.getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
}
- ((ViewGroup.MarginLayoutParams) mWorkModeSwitch.getLayoutParams()).bottomMargin =
- workFabMarginBottom;
+ if (!mAllApps.mActivityContext.getDeviceProfile().isGestureMode){
+ workFabMarginBottom += mAllApps.mActivityContext.getDeviceProfile().getInsets().bottom;
+ }
+ lp.bottomMargin = workFabMarginBottom;
+ int totalScreenWidth = mDeviceProfile.widthPx;
+ int personalWorkTabWidth =
+ mAllApps.mActivityContext.getAppsView().getActiveRecyclerView().getTabWidth();
+ lp.rightMargin = lp.leftMargin = (totalScreenWidth - personalWorkTabWidth) / 2;
if (mWorkModeSwitch.getParent() != mAllApps) {
mAllApps.addView(mWorkModeSwitch);
}
@@ -158,7 +171,6 @@
mWorkModeSwitch.updateCurrentState(mCurrentState == STATE_ENABLED);
return true;
}
-
/**
* Removes work profile toggle button from {@link BaseAllAppsContainerView}
*/
@@ -173,7 +185,7 @@
return mAdapterProvider;
}
- public ItemInfoMatcher getMatcher() {
+ public Predicate<ItemInfo> getMatcher() {
return mMatcher;
}
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 893e547..6539c05 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -38,7 +38,6 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.search.SearchCallback;
@@ -57,7 +56,6 @@
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
- private AlphabeticalAppsList<?> mApps;
private ActivityAllAppsContainerView<?> mAppsView;
// The amount of pixels to shift down and overlap with the rest of the content.
@@ -131,7 +129,6 @@
@Override
public void initializeSearch(ActivityAllAppsContainerView<?> appsView) {
- mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
new DefaultAppSearchAlgorithm(getContext()),
@@ -170,25 +167,14 @@
@Override
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
- mApps.setSearchResults(items);
- notifyResultChanged();
+ mAppsView.setSearchResults(items);
mAppsView.setLastSearchQuery(query);
}
}
@Override
- public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
- if (items != null) {
- mApps.appendSearchResults(items);
- notifyResultChanged();
- }
- }
-
- @Override
public void clearSearchResult() {
- if (mApps.setSearchResults(null)) {
- notifyResultChanged();
- }
+ mAppsView.setSearchResults(null);
// Clear the search query
mSearchQueryBuilder.clear();
@@ -197,10 +183,6 @@
mAppsView.onClearSearchResult();
}
- private void notifyResultChanged() {
- mAppsView.onSearchResultsChanged();
- }
-
@Override
public void setInsets(Rect insets) {
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 222c8fe..4eceb71 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -85,8 +85,7 @@
for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
AppInfo info = apps.get(i);
if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
- AdapterItem appItem = AdapterItem.asApp(resultCount, "", info, resultCount);
- result.add(appItem);
+ result.add(AdapterItem.asApp(info));
resultCount++;
}
}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 5a46ce1..c38cf9a 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -47,13 +47,6 @@
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
- /**
- * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
- * is appearing e.g. when coming from off screen
- */
- public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
- 0.05f, 0.7f, 0.1f, 1f);
-
public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -64,6 +57,11 @@
public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f);
public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f);
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+
public static final Interpolator EXAGGERATED_EASE;
public static final Interpolator INSTANT = t -> 1;
@@ -163,14 +161,18 @@
String.format("upperBound (%f) must be greater than lowerBound (%f)",
upperBound, lowerBound));
}
- return t -> clampToProgress(t, lowerBound, upperBound);
+ return t -> clampToProgress(interpolator, t, lowerBound, upperBound);
}
/**
* Returns the progress value's progress between the lower and upper bounds. That is, the
* progress will be 0f from 0f to lowerBound, and reach 1f by upperBound.
+ *
+ * Between lowerBound and upperBound, the progress value will be interpolated using the provided
+ * interpolator.
*/
- public static float clampToProgress(float progress, float lowerBound, float upperBound) {
+ public static float clampToProgress(
+ Interpolator interpolator, float progress, float lowerBound, float upperBound) {
if (upperBound < lowerBound) {
throw new IllegalArgumentException(
String.format("upperBound (%f) must be greater than lowerBound (%f)",
@@ -186,7 +188,15 @@
if (progress > upperBound) {
return 1;
}
- return (progress - lowerBound) / (upperBound - lowerBound);
+ return interpolator.getInterpolation((progress - lowerBound) / (upperBound - lowerBound));
+ }
+
+ /**
+ * Returns the progress value's progress between the lower and upper bounds. That is, the
+ * progress will be 0f from 0f to lowerBound, and reach 1f by upperBound.
+ */
+ public static float clampToProgress(float progress, float lowerBound, float upperBound) {
+ return clampToProgress(Interpolators.LINEAR, progress, lowerBound, upperBound);
}
/**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 626e15c..9775b87 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -246,6 +246,10 @@
"ENABLE_ALL_APPS_IN_TASKBAR", true,
"Enables accessing All Apps from the system Taskbar.");
+ public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(
+ "ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT", false,
+ "Enables displaying the all apps button in the hotseat.");
+
public static final BooleanFlag ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR = getDebugFlag(
"ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR", false,
"Enables One Search box in Taskbar All Apps.");
@@ -261,6 +265,10 @@
public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
"ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
+ public static final BooleanFlag ENABLE_SHOW_KEYBOARD_IN_ALL_APPS = getDebugFlag(
+ "ENABLE_SHOW_KEYBOARD_IN_ALL_APPS", false,
+ "Enable option to show keyboard when going to all-apps");
+
public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(
"USE_LOCAL_ICON_OVERRIDES", true,
"Use inbuilt monochrome icons if app doesn't provide one");
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index fdb2799..35cdfef 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Utilities.ATLEAST_Q;
-import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -36,12 +35,12 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.Optional;
+import java.util.function.Predicate;
/**
* Class for initiating a drag within a view or across multiple views.
@@ -275,15 +274,12 @@
protected abstract void exitDrag();
- public void onAppsRemoved(ItemInfoMatcher matcher) {
+ public void onAppsRemoved(Predicate<ItemInfo> matcher) {
// Cancel the current drag if we are removing an app that we are dragging
if (mDragObject != null) {
ItemInfo dragInfo = mDragObject.dragInfo;
- if (dragInfo instanceof WorkspaceItemInfo) {
- ComponentName cn = dragInfo.getTargetComponent();
- if (cn != null && matcher.matches(dragInfo, cn)) {
- cancelDrag();
- }
+ if (dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo)) {
+ cancelDrag();
}
}
}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 14e10918..8eeca7d 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -65,9 +65,7 @@
public class DragLayer extends BaseDragLayer<Launcher> {
public static final int ALPHA_INDEX_OVERLAY = 0;
- public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
- public static final int ALPHA_INDEX_TRANSITIONS = 2;
- private static final int ALPHA_CHANNEL_COUNT = 3;
+ private static final int ALPHA_CHANNEL_COUNT = 1;
public static final int ANIMATION_END_DISAPPEAR = 0;
public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index a3945fd..0264ae2 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -578,4 +578,19 @@
iv.setImageDrawable(drawable);
return iv;
}
+
+ /**
+ * Removes any stray DragView from the DragLayer.
+ */
+ public static void removeAllViews(ActivityContext activity) {
+ BaseDragLayer dragLayer = activity.getDragLayer();
+ // Iterate in reverse order. DragView is added later to the dragLayer,
+ // and will be one of the last views.
+ for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof DragView) {
+ dragLayer.removeView(child);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 74d9a22..6f295e6 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -20,9 +20,13 @@
import android.annotation.TargetApi;
import android.graphics.Bitmap;
-import android.graphics.Matrix;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.Picture;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -31,10 +35,11 @@
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import com.android.launcher3.Utilities;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
-import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.views.ActivityContext;
@@ -69,79 +74,104 @@
return mBadge;
}
+ @TargetApi(Build.VERSION_CODES.P)
public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
- ActivityContext activity, int folderId, Point dragViewSize) {
+ ActivityContext activity, int folderId, Point size) {
Preconditions.assertNonUiThread();
+ if (!Utilities.ATLEAST_P) {
+ return null;
+ }
- // Create the actual drawable on the UI thread to avoid race conditions with
+ // assume square
+ if (size.x != size.y) {
+ return null;
+ }
+ int requestedSize = size.x;
+
+ // Only use the size actually needed for drawing the folder icon
+ int drawingSize = activity.getDeviceProfile().folderIconSizePx;
+ int foregroundSize = Math.max(requestedSize, drawingSize);
+ float shift = foregroundSize - requestedSize;
+
+ Picture background = new Picture();
+ Picture foreground = new Picture();
+ Picture badge = new Picture();
+
+ Canvas bgCanvas = background.beginRecording(requestedSize, requestedSize);
+ Canvas badgeCanvas = badge.beginRecording(requestedSize, requestedSize);
+
+ Canvas fgCanvas = foreground.beginRecording(foregroundSize, foregroundSize);
+ fgCanvas.translate(shift, shift);
+
+ // Do not clip the folder drawing since the icon previews extend outside the background.
+ Path mask = new Path();
+ mask.addRect(-shift, -shift, requestedSize + shift, requestedSize + shift,
+ Direction.CCW);
+
+ // Initialize the actual draw commands on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
- return MAIN_EXECUTOR.submit(() -> {
+ MAIN_EXECUTOR.submit(() -> {
FolderIcon icon = activity.findFolderIcon(folderId);
- return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
-
+ if (icon == null) {
+ throw new IllegalArgumentException("Folder not found with id: " + folderId);
+ }
+ initLayersOnUiThread(icon, requestedSize, bgCanvas, fgCanvas, badgeCanvas);
}).get();
} catch (Exception e) {
Log.e(TAG, "Unable to create folder icon", e);
return null;
+ } finally {
+ background.endRecording();
+ foreground.endRecording();
+ badge.endRecording();
}
+
+ // Only convert foreground to a bitmap as it can contain multiple draw commands. Other
+ // layers either draw a nothing or a single draw call.
+ Bitmap fgBitmap = Bitmap.createBitmap(foreground);
+ Paint foregroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ // Do not use PictureDrawable as it moves the picture to the canvas bounds, whereas we want
+ // to draw it at (0,0)
+ return new FolderAdaptiveIcon(
+ new BitmapRendererDrawable(c -> c.drawPicture(background)),
+ new BitmapRendererDrawable(
+ c -> c.drawBitmap(fgBitmap, -shift, -shift, foregroundPaint)),
+ new BitmapRendererDrawable(c -> c.drawPicture(badge)),
+ mask);
}
- private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
- Point dragViewSize) {
- Preconditions.assertUIThread();
-
+ @UiThread
+ private static void initLayersOnUiThread(FolderIcon icon, int size,
+ Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas) {
icon.getPreviewBounds(sTmpRect);
-
- PreviewBackground bg = icon.getFolderBackground();
-
- // assume square
- assert (dragViewSize.x == dragViewSize.y);
final int previewSize = sTmpRect.width();
- final int margin = (dragViewSize.x - previewSize) / 2;
+ PreviewBackground bg = icon.getFolderBackground();
+ final int margin = (size - previewSize) / 2;
final float previewShiftX = -sTmpRect.left + margin;
final float previewShiftY = -sTmpRect.top + margin;
// Initialize badge, which consists of the outline stroke, shadow and dot; these
// must be rendered above the foreground
- Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
- (canvas) -> {
- canvas.save();
- canvas.translate(previewShiftX, previewShiftY);
- bg.drawShadow(canvas);
- bg.drawBackgroundStroke(canvas);
- icon.drawDot(canvas);
- canvas.restore();
- });
+ badgeCanvas.save();
+ badgeCanvas.translate(previewShiftX, previewShiftY);
+ icon.drawDot(badgeCanvas);
+ badgeCanvas.restore();
- // Initialize mask
- Path mask = new Path();
- Matrix m = new Matrix();
- m.setTranslate(previewShiftX, previewShiftY);
- bg.getClipPath().transform(m, mask);
+ // Draw foreground
+ foregroundCanvas.save();
+ foregroundCanvas.translate(previewShiftX, previewShiftY);
+ icon.getPreviewItemManager().draw(foregroundCanvas);
+ foregroundCanvas.restore();
- Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
- (canvas) -> {
- canvas.save();
- canvas.translate(previewShiftX, previewShiftY);
- icon.getPreviewItemManager().draw(canvas);
- canvas.restore();
- });
-
- Bitmap bgBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
- (canvas) -> {
- Paint p = new Paint();
- p.setColor(bg.getBgColor());
-
- canvas.drawCircle(dragViewSize.x / 2f, dragViewSize.y / 2f, bg.getRadius(), p);
- });
-
- ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
- ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
- ShiftedBitmapDrawable background = new ShiftedBitmapDrawable(bgBitmap, 0, 0);
-
- return new FolderAdaptiveIcon(background, foreground, badge, mask);
+ // Draw background
+ Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ backgroundPaint.setColor(bg.getBgColor());
+ bg.drawShadow(backgroundCanvas);
+ backgroundCanvas.drawCircle(size / 2f, size / 2f, bg.getRadius(), backgroundPaint);
+ bg.drawBackgroundStroke(backgroundCanvas);
}
@Override
@@ -174,4 +204,52 @@
& mBadge.getChangingConfigurations();
}
}
+
+ private static class BitmapRendererDrawable extends Drawable {
+
+ private final BitmapRenderer mRenderer;
+
+ BitmapRendererDrawable(BitmapRenderer renderer) {
+ mRenderer = renderer;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mRenderer.draw(canvas);
+ }
+
+ @Override
+ public void setAlpha(int i) { }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) { }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new MyConstantState(mRenderer);
+ }
+
+ private static class MyConstantState extends ConstantState {
+ private final BitmapRenderer mRenderer;
+
+ MyConstantState(BitmapRenderer renderer) {
+ mRenderer = renderer;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new BitmapRendererDrawable(mRenderer);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 0;
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 29e7c18..f9916d0 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -87,7 +87,8 @@
// Total duration for the drop animation to complete.
long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY +
- LauncherState.SPRING_LOADED.getTransitionDuration(Launcher.getLauncher(mContext));
+ LauncherState.SPRING_LOADED.getTransitionDuration(Launcher.getLauncher(mContext),
+ true /* isToState */);
// Delay the actual accept() call until the drop animation is complete.
return PinRequestHelper.createWorkspaceItemFromPinItemRequest(
mContext, mRequest, duration);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 8916519..e68ebdb 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -72,7 +72,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
@@ -88,10 +87,10 @@
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
@@ -145,7 +144,7 @@
* Time for which the scroll hint is shown before automatically changing page.
*/
public static final int SCROLL_HINT_DURATION = 500;
- public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+ private static final int RESCROLL_EXTRA_DELAY = 150;
public static final int SCROLL_NONE = -1;
public static final int SCROLL_LEFT = 0;
@@ -1283,9 +1282,9 @@
final WorkspaceItemInfo si;
if (pasiSi != null) {
si = pasiSi;
- } else if (d.dragInfo instanceof AppInfo) {
+ } else if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy.
- si = ((AppInfo) d.dragInfo).makeWorkspaceItem();
+ si = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(launcher);
} else {
// WorkspaceItemInfo
si = (WorkspaceItemInfo) d.dragInfo;
@@ -1523,7 +1522,9 @@
// Pause drag event until the scrolling is finished
mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
- mScrollPauseAlarm.setAlarm(RESCROLL_DELAY);
+ int rescrollDelay = getResources().getInteger(
+ R.integer.config_pageSnapAnimationDuration) + RESCROLL_EXTRA_DELAY;
+ mScrollPauseAlarm.setAlarm(rescrollDelay);
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 5fe2435..b1e2701 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -68,11 +68,11 @@
import com.android.launcher3.logger.LauncherAtom.ToState;
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.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.FolderInfo.LabelState;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Executors;
@@ -284,7 +284,7 @@
mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
if (SPRING_LOADING_ENABLED &&
- ((dragInfo instanceof AppInfo)
+ ((dragInfo instanceof WorkspaceItemFactory)
|| (dragInfo instanceof WorkspaceItemInfo)
|| (dragInfo instanceof PendingAddShortcutInfo))) {
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
@@ -486,9 +486,9 @@
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
- if (d.dragInfo instanceof AppInfo) {
+ if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy
- item = ((AppInfo) d.dragInfo).makeWorkspaceItem();
+ item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
} else if (d.dragSource instanceof BaseItemDragListener){
// Came from a different window -- make a copy
item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
@@ -637,7 +637,7 @@
// If we are animating to the accepting state, animate the dot out.
mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
- mDotParams.color = mBackground.getDotColor();
+ mDotParams.dotColor = mBackground.getDotColor();
mDotRenderer.draw(canvas, mDotParams);
}
}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 2b621bd..5021644 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.folder;
+import android.annotation.SuppressLint;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Process;
@@ -22,11 +24,15 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -94,6 +100,7 @@
/**
* Generate and rank the suggested Folder names.
*/
+ @WorkerThread
public void getSuggestedFolderName(Context context,
ArrayList<WorkspaceItemInfo> workspaceItemInfos,
FolderNameInfos nameInfos) {
@@ -107,8 +114,7 @@
Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
.collect(Collectors.toSet());
if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
- String workFolderName = context.getString(R.string.work_folder_name);
- setAsLastSuggestion(nameInfos, workFolderName);
+ setAsLastSuggestion(nameInfos, getWorkFolderName(context));
}
// If all the icons are from same package (e.g., main icon, shortcut, shortcut)
@@ -130,6 +136,17 @@
}
}
+ @WorkerThread
+ @SuppressLint("NewApi")
+ private String getWorkFolderName(Context context) {
+ if (!Utilities.ATLEAST_T) {
+ return context.getString(R.string.work_folder_name);
+ }
+ return context.getSystemService(DevicePolicyManager.class).getResources()
+ .getString(StringCache.WORK_FOLDER_NAME, () ->
+ context.getString(R.string.work_folder_name));
+ }
+
private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
if (mAppInfos == null || mAppInfos.isEmpty()) {
return Optional.empty();
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3dfece7..d5bcb0c 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -23,7 +23,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
-import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import android.annotation.TargetApi;
import android.app.Fragment;
@@ -84,6 +83,7 @@
import com.android.launcher3.uioverrides.PredictedAppIconInflater;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
@@ -130,7 +130,7 @@
super(base, UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE,
- WindowManagerProxy.INSTANCE);
+ WindowManagerProxy.INSTANCE, DisplayController.INSTANCE);
mIdp = idp;
mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
mObjectMap.put(LauncherAppState.INSTANCE,
@@ -420,8 +420,6 @@
currentWorkspaceItems, otherWorkspaceItems);
filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
otherAppWidgets);
-
- sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
for (ItemInfo itemInfo : currentWorkspaceItems) {
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
deleted file mode 100644
index f8583b8..0000000
--- a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.graphics;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-
-/**
- * A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
- */
-public class ShiftedBitmapDrawable extends Drawable {
-
- private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
- private final Bitmap mBitmap;
- private float mShiftX;
- private float mShiftY;
-
- private final ConstantState mConstantState;
-
- public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
- mBitmap = bitmap;
- mShiftX = shiftX;
- mShiftY = shiftY;
-
- mConstantState = new MyConstantState(mBitmap, mShiftX, mShiftY);
- }
-
- public float getShiftX() {
- return mShiftX;
- }
-
- public float getShiftY() {
- return mShiftY;
- }
-
- public void setShiftX(float shiftX) {
- mShiftX = shiftX;
- }
-
- public void setShiftY(float shiftY) {
- mShiftY = shiftY;
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
- }
-
- @Override
- public void setAlpha(int i) { }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- mPaint.setColorFilter(colorFilter);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public ConstantState getConstantState() {
- return mConstantState;
- }
-
- private static class MyConstantState extends ConstantState {
- private final Bitmap mBitmap;
- private float mShiftX;
- private float mShiftY;
-
- MyConstantState(Bitmap bitmap, float shiftX, float shiftY) {
- mBitmap = bitmap;
- mShiftX = shiftX;
- mShiftY = shiftY;
- }
-
- @Override
- public Drawable newDrawable() {
- return new ShiftedBitmapDrawable(mBitmap, mShiftX, mShiftY);
- }
-
- @Override
- public int getChangingConfigurations() {
- return 0;
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 7ba2317..fe9b633 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -42,8 +42,10 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.android.launcher3.InvariantDeviceProfile;
@@ -94,6 +96,8 @@
private final InstantAppResolver mInstantAppResolver;
private final IconProvider mIconProvider;
+ private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
+
private int mPendingIconRequestCount = 0;
public IconCache(Context context, InvariantDeviceProfile idp) {
@@ -111,6 +115,7 @@
mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
mIconProvider = iconProvider;
+ mWidgetCategoryBitmapInfos = new SparseArray<>();
}
@Override
@@ -477,13 +482,39 @@
CacheEntry entry = getEntryForPackageLocked(
infoInOut.packageName, infoInOut.user, useLowResIcon);
applyCacheEntry(entry, infoInOut);
- if (infoInOut.widgetCategory != NO_CATEGORY) {
- WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
- .get(infoInOut.widgetCategory);
- infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
- infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(
- infoInOut.title, infoInOut.user);
+ if (infoInOut.widgetCategory == NO_CATEGORY) {
+ return;
}
+
+ WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
+ .get(infoInOut.widgetCategory);
+ infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
+ infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(
+ infoInOut.title, infoInOut.user);
+ final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory);
+ if (cachedBitmap != null) {
+ infoInOut.bitmap = getBadgedIcon(cachedBitmap, infoInOut.user);
+ return;
+ }
+
+ try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+ final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
+ mContext.getDrawable(widgetSection.mSectionDrawable),
+ new BaseIconFactory.IconOptions().setShrinkNonAdaptiveIcons(false));
+ mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap);
+ infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user);
+ } catch (Exception e) {
+ Log.e(TAG, "Error initializing bitmap for icons with widget category", e);
+ }
+
+ }
+
+ private synchronized BitmapInfo getBadgedIcon(@Nullable final BitmapInfo bitmap,
+ @NonNull final UserHandle user) {
+ if (bitmap == null) {
+ return getDefaultIcon(user);
+ }
+ return bitmap.withFlags(getUserFlagOpLocked(user));
}
protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9af72c3..f4f270c 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -586,6 +586,9 @@
@UiEvent(doc = "User clicked on IME quicksearch button.")
LAUNCHER_ALLAPPS_QUICK_SEARCH_WITH_IME(1047),
+
+ @UiEvent(doc = "User tapped taskbar All Apps button.")
+ LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP(1057),
;
// ADD MORE
@@ -728,7 +731,8 @@
enum LatencyType {
UNKNOWN(0),
COLD(1),
- HOT(2);
+ HOT(2),
+ TIMEOUT(3);
private final int mId;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index ca91296..31ef2e5 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -32,6 +32,7 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemFactory;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.PackageInstallInfo;
@@ -107,8 +108,8 @@
}
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- if (item instanceof AppInfo) {
- item = ((AppInfo) item).makeWorkspaceItem();
+ if (item instanceof WorkspaceItemFactory) {
+ item = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
}
}
if (item != null) {
@@ -130,8 +131,8 @@
if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
item instanceof LauncherAppWidgetInfo) {
itemInfo = item;
- } else if (item instanceof AppInfo) {
- itemInfo = ((AppInfo) item).makeWorkspaceItem();
+ } else if (item instanceof WorkspaceItemFactory) {
+ itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
} else {
throw new RuntimeException("Unexpected info type");
}
@@ -180,7 +181,7 @@
// App was installed while launcher was in the background,
// or app was already installed for another user.
itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
- .makeWorkspaceItem();
+ .makeWorkspaceItem(app.getContext());
if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
// We need this additional check here since we treat all auto added
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index b8c9762..4875d83 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -36,9 +36,9 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SafeCloseable;
@@ -47,6 +47,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
@@ -257,11 +258,11 @@
/**
* Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
*/
- public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) {
+ public void updateDisabledFlags(Predicate<ItemInfo> matcher, FlagOp op) {
final List<AppInfo> data = this.data;
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
- if (matcher.matches(info, info.componentName)) {
+ if (matcher.test(info)) {
info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
mDataChanged = true;
}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 5b278ab..b50ab58 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.os.Process;
@@ -27,6 +26,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
@@ -42,6 +43,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -110,6 +112,42 @@
public abstract void bindWidgets();
+ /**
+ * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+ */
+ protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+ ArrayList<ItemInfo> workspaceItems) {
+ final int screenCols = profile.numColumns;
+ final int screenCellCount = profile.numColumns * profile.numRows;
+ Collections.sort(workspaceItems, (lhs, rhs) -> {
+ if (lhs.container == rhs.container) {
+ // Within containers, order by their spatial position in that container
+ switch (lhs.container) {
+ case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+ int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
+ + lhs.cellX);
+ int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
+ + rhs.cellX);
+ return Integer.compare(lr, rr);
+ }
+ case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+ // We currently use the screen id as the rank
+ return Integer.compare(lhs.screenId, rhs.screenId);
+ }
+ default:
+ if (FeatureFlags.IS_STUDIO_BUILD) {
+ throw new RuntimeException(
+ "Unexpected container type when sorting workspace items.");
+ }
+ return 0;
+ }
+ } else {
+ // Between containers, order by hotseat, desktop
+ return Integer.compare(lhs.container, rhs.container);
+ }
+ });
+ }
+
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
@@ -131,7 +169,7 @@
return idleLock;
}
- private static class WorkspaceBinder {
+ private class WorkspaceBinder {
private final Executor mUiExecutor;
private final Callbacks mCallbacks;
@@ -166,7 +204,9 @@
}
private void bind() {
- IntSet currentScreenIds = mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
+ final IntSet currentScreenIds =
+ mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
+ Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks);
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index a3a4717..832c1dd 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -27,7 +27,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.util.ArrayList;
@@ -35,6 +34,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -128,7 +128,7 @@
scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
}
- public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) {
+ public void deleteAndBindComponentsRemoved(final Predicate<ItemInfo> matcher) {
getModelWriter().deleteItemsFromDatabase(matcher);
// Call the components-removed callback
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 866d18a..de23c4b 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -31,6 +31,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings;
@@ -50,7 +51,6 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -66,6 +66,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -469,6 +470,7 @@
* or an empty IntSet
* @param orderedScreenIds All the page ids to be bound
*/
+ @NonNull
default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
return new IntSet();
}
@@ -495,7 +497,7 @@
default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
- default void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+ default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index e36d4cf..ef9250c 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -216,14 +216,15 @@
return false;
}
+ // Sort the items by the reading order.
+ Collections.sort(mHotseatDiff);
+ Collections.sort(mWorkspaceDiff);
+
// Migrate hotseat
HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
hotseatSolution.find();
- // Sort the items by the reading order.
- Collections.sort(mWorkspaceDiff);
-
// Migrate workspace.
// First we create a collection of the screens
List<Integer> screens = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index ef5eef1..422af43 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -23,10 +23,8 @@
import android.os.Process;
import android.util.Log;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
@@ -53,7 +51,7 @@
* specified screen.
*/
public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
- IntSet currentScreenIds,
+ final IntSet currentScreenIds,
ArrayList<T> allWorkspaceItems,
ArrayList<T> currentScreenItems,
ArrayList<T> otherScreenItems) {
@@ -92,42 +90,6 @@
}
/**
- * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
- */
- public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
- ArrayList<ItemInfo> workspaceItems) {
- final int screenCols = profile.numColumns;
- final int screenCellCount = profile.numColumns * profile.numRows;
- Collections.sort(workspaceItems, (lhs, rhs) -> {
- if (lhs.container == rhs.container) {
- // Within containers, order by their spatial position in that container
- switch (lhs.container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
- int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
- + lhs.cellX);
- int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
- + rhs.cellX);
- return Integer.compare(lr, rr);
- }
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
- // We currently use the screen id as the rank
- return Integer.compare(lhs.screenId, rhs.screenId);
- }
- default:
- if (FeatureFlags.IS_STUDIO_BUILD) {
- throw new RuntimeException(
- "Unexpected container type when sorting workspace items.");
- }
- return 0;
- }
- } else {
- // Between containers, order by hotseat, desktop
- return Integer.compare(lhs.container, rhs.container);
- }
- });
- }
-
- /**
* Iterates though current workspace items and returns available hotseat ranks for prediction.
*/
public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 94e06d1..015abe9 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -53,6 +53,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -278,10 +279,9 @@
/**
* Removes all the items from the database matching {@param matcher}.
*/
- public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
+ public void deleteItemsFromDatabase(Predicate<ItemInfo> matcher) {
deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false)
- .filter(matcher::matchesInfo)
- .collect(Collectors.toList()));
+ .filter(matcher).collect(Collectors.toList()));
}
/**
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d47edff..239dd45 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -57,6 +57,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.function.Predicate;
/**
* Handles updates due to changes in package manager (app installed/updated/removed)
@@ -95,7 +96,7 @@
final int N = packages.length;
final FlagOp flagOp;
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
- final ItemInfoMatcher matcher = mOp == OP_USER_AVAILABILITY_CHANGE
+ final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE
? ItemInfoMatcher.ofUser(mUser) // We want to update all packages for this user
: ItemInfoMatcher.ofPackages(packageSet, mUser);
final HashSet<ComponentName> removedComponents = new HashSet<>();
@@ -206,7 +207,7 @@
}
ComponentName cn = si.getTargetComponent();
- if (cn != null && matcher.matches(si, cn)) {
+ if (cn != null && matcher.test(si)) {
String packageName = cn.getPackageName();
if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
@@ -336,7 +337,7 @@
}
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
- ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
+ Predicate<ItemInfo> removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
deleteAndBindComponentsRemoved(removeMatch);
diff --git a/src/com/android/launcher3/model/ReloadStringCacheTask.java b/src/com/android/launcher3/model/ReloadStringCacheTask.java
new file mode 100644
index 0000000..f4d4298
--- /dev/null
+++ b/src/com/android/launcher3/model/ReloadStringCacheTask.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import com.android.launcher3.LauncherAppState;
+
+/**
+ * Handles updates due to changes in Device Policy Management resources triggered by
+ * {@link android.app.admin.DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED}.
+ */
+public class ReloadStringCacheTask extends BaseModelUpdateTask {
+ private ModelDelegate mModelDelegate;
+
+ public ReloadStringCacheTask(ModelDelegate modelDelegate) {
+ mModelDelegate = modelDelegate;
+ }
+
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+ synchronized (dataModel) {
+ mModelDelegate.loadStringCache(dataModel.stringCache);
+ StringCache cloneSC = dataModel.stringCache.clone();
+ scheduleCallbackTask(c -> c.bindStringCache(cloneSC));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/StringCache.java b/src/com/android/launcher3/model/StringCache.java
index 2fc852d..9859ddc 100644
--- a/src/com/android/launcher3/model/StringCache.java
+++ b/src/com/android/launcher3/model/StringCache.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.Build;
@@ -34,6 +35,11 @@
private static final String PREFIX = "Launcher.";
/**
+ * Work folder name.
+ */
+ public static final String WORK_FOLDER_NAME = PREFIX + "WORK_FOLDER_NAME";
+
+ /**
* User on-boarding title for work profile apps.
*/
private static final String WORK_PROFILE_EDU = PREFIX + "WORK_PROFILE_EDU";
@@ -90,11 +96,6 @@
PREFIX + "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY";
/**
- * Work folder name.
- */
- private static final String WORK_FOLDER_NAME = PREFIX + "WORK_FOLDER_NAME";
-
- /**
* Label on widget tab to indicate work app widgets.
*/
private static final String WIDGETS_WORK_TAB = PREFIX + "WIDGETS_WORK_TAB";
@@ -215,6 +216,7 @@
context, DISABLED_BY_ADMIN_MESSAGE, R.string.msg_disabled_by_admin);
}
+ @SuppressLint("NewApi")
private String getEnterpriseString(
Context context, String updatableStringId, int defaultStringId) {
return Utilities.ATLEAST_T
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 7f70bad..5b2bcf5 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -35,7 +35,6 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.Comparator;
@@ -43,7 +42,7 @@
/**
* Represents an app in AllAppsView.
*/
-public class AppInfo extends ItemInfoWithIcon {
+public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
public static final Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
@@ -121,7 +120,8 @@
return super.dumpProperties() + " componentName=" + componentName;
}
- public WorkspaceItemInfo makeWorkspaceItem() {
+ @Override
+ public WorkspaceItemInfo makeWorkspaceItem(Context context) {
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(this);
if ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
@@ -139,10 +139,6 @@
return workspaceItemInfo;
}
- public ComponentKey toComponentKey() {
- return new ComponentKey(componentName, user);
- }
-
public static Intent makeLaunchIntent(LauncherActivityInfo info) {
return makeLaunchIntent(info.getComponentName());
}
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 19345d7..76a0c4d 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -71,10 +71,6 @@
*/
public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
- public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE
- | FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED
- | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
-
/**
* The item points to a system app.
*/
@@ -114,6 +110,16 @@
| FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
/**
+ * Indicates that the icon is a disabled shortcut and application updates are required.
+ */
+ public static final int FLAG_DISABLED_VERSION_LOWER = 1 << 12;
+
+ public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE
+ | FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED
+ | FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER
+ | FLAG_DISABLED_VERSION_LOWER;
+
+ /**
* 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.
*/
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index cc22601..e879313 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Process;
@@ -26,18 +27,14 @@
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.logger.LauncherAtom.ItemInfo;
import com.android.launcher3.logger.LauncherAtom.SearchActionItem;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
/**
* Represents a SearchAction with in launcher
*/
-public class SearchActionItemInfo extends ItemInfoWithIcon {
+public class SearchActionItemInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
public static final int FLAG_SHOULD_START = 1 << 1;
public static final int FLAG_SHOULD_START_FOR_RESULT = FLAG_SHOULD_START | 1 << 2;
@@ -158,7 +155,8 @@
/**
* Creates a {@link WorkspaceItemInfo} coorsponding to search action to be stored in launcher db
*/
- public WorkspaceItemInfo createWorkspaceItem(LauncherModel model) {
+ @Override
+ public WorkspaceItemInfo makeWorkspaceItem(Context context) {
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.title = title;
info.bitmap = bitmap;
@@ -167,18 +165,12 @@
if (hasFlags(FLAG_SHOULD_START_FOR_RESULT)) {
info.options |= WorkspaceItemInfo.FLAG_START_FOR_RESULT;
}
-
- model.enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-
- model.updateAndBindWorkspaceItem(() -> {
- PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
- app.getIconCache().getTitleAndIconForApp(pkgInfo, false);
- info.bitmap = info.bitmap.withBadgeInfo(pkgInfo.bitmap);
- return info;
- });
- }
+ LauncherAppState app = LauncherAppState.getInstance(context);
+ app.getModel().updateAndBindWorkspaceItem(() -> {
+ PackageItemInfo pkgInfo = new PackageItemInfo(getIntentPackageName(), user);
+ app.getIconCache().getTitleAndIconForApp(pkgInfo, false);
+ info.bitmap = info.bitmap.withBadgeInfo(pkgInfo.bitmap);
+ return info;
});
return info;
}
diff --git a/src/com/android/launcher3/allapps/DecorationInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemFactory.java
similarity index 61%
copy from src/com/android/launcher3/allapps/DecorationInfo.java
copy to src/com/android/launcher3/model/data/WorkspaceItemFactory.java
index 50b250c..47b9c6e 100644
--- a/src/com/android/launcher3/allapps/DecorationInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -13,7 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.allapps;
+package com.android.launcher3.model.data;
-public class DecorationInfo {
+import android.content.Context;
+
+/**
+ * Interface to objects capable of generating workspace item
+ */
+public interface WorkspaceItemFactory {
+
+ /**
+ * Called to create a pinnable item info
+ */
+ WorkspaceItemInfo makeWorkspaceItem(Context context);
}
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index a195979..2b3da33 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -180,12 +180,25 @@
runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
}
disabledMessage = shortcutInfo.getDisabledMessage();
+ if (Utilities.ATLEAST_P
+ && shortcutInfo.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+ runtimeStatusFlags |= FLAG_DISABLED_VERSION_LOWER;
+ } else {
+ runtimeStatusFlags &= ~FLAG_DISABLED_VERSION_LOWER;
+ }
Person[] persons = ApiWrapper.getPersons(shortcutInfo);
personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
: Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
}
+ /**
+ * {@code true} if the shortcut is disabled due to its app being a lower version.
+ */
+ public boolean isDisabledVersionLower() {
+ return (runtimeStatusFlags & FLAG_DISABLED_VERSION_LOWER) != 0;
+ }
+
/** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
public String getDeepShortcutId() {
return itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index bbeb886..04eb38a 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -35,6 +35,7 @@
import android.util.Pair;
import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -154,14 +155,9 @@
case MSG_NOTIFICATION_FULL_REFRESH:
List<StatusBarNotification> activeNotifications = null;
if (sIsConnected) {
- try {
- activeNotifications = Arrays.stream(getActiveNotifications())
- .filter(this::notificationIsValidForUI)
- .collect(Collectors.toList());
- } catch (SecurityException ex) {
- Log.e(TAG, "SecurityException: failed to fetch notifications");
- activeNotifications = new ArrayList<>();
- }
+ activeNotifications = Arrays.stream(getActiveNotificationsSafely(null))
+ .filter(this::notificationIsValidForUI)
+ .collect(Collectors.toList());
} else {
activeNotifications = new ArrayList<>();
}
@@ -175,7 +171,7 @@
}
case MSG_RANKING_UPDATE: {
String[] keys = ((RankingMap) message.obj).getOrderedKeys();
- for (StatusBarNotification sbn : getActiveNotifications(keys)) {
+ for (StatusBarNotification sbn : getActiveNotificationsSafely(keys)) {
updateGroupKeyIfNecessary(sbn);
}
return true;
@@ -214,6 +210,16 @@
return true;
}
+ private @NonNull StatusBarNotification[] getActiveNotificationsSafely(@Nullable String[] keys) {
+ StatusBarNotification[] result = null;
+ try {
+ result = getActiveNotifications(keys);
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException: failed to fetch notifications");
+ }
+ return result == null ? new StatusBarNotification[0] : result;
+ }
+
@Override
public void onListenerConnected() {
super.onListenerConnected();
@@ -313,9 +319,8 @@
*/
@WorkerThread
public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
- StatusBarNotification[] notifications = getActiveNotifications(
- keys.stream().map(n -> n.notificationKey).toArray(String[]::new));
- return notifications == null ? Collections.emptyList() : Arrays.asList(notifications);
+ return Arrays.asList(getActiveNotificationsSafely(
+ keys.stream().map(n -> n.notificationKey).toArray(String[]::new)));
}
/**
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index df8a3e2..196cc56 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -602,6 +602,7 @@
mIsAboveIcon = y > dragLayer.getTop() + insets.top;
if (!mIsAboveIcon) {
y = mTempRect.top + iconHeight + extraVerticalSpace;
+ height -= extraVerticalSpace;
}
// Insets are added later, so subtract them now.
@@ -609,7 +610,7 @@
y -= insets.top;
mGravity = 0;
- if (y + height > dragLayer.getBottom() - insets.bottom) {
+ if ((insets.top + y + height) > (dragLayer.getBottom() - insets.bottom)) {
// The container is opening off the screen, so just center it in the drag layer instead.
mGravity = Gravity.CENTER_VERTICAL;
// Put the container next to the icon, preferring the right side in ltr (left in rtl).
diff --git a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
index 72956b0..c0a04b1 100644
--- a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
+++ b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
@@ -45,8 +45,9 @@
@Override
public void onWidgetsBound() {
- ItemInfo itemInfo = (ItemInfo) mPopupContainerWithArrow.getOriginalIcon().getTag();
- SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mContext, itemInfo);
+ BubbleTextView originalIcon = mPopupContainerWithArrow.getOriginalIcon();
+ SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mContext,
+ (ItemInfo) originalIcon.getTag(), originalIcon);
View widgetsView = getWidgetsView(mPopupContainerWithArrow);
if (widgetsView == null && mPopupContainerWithArrow.getWidgetContainer() != null) {
widgetsView = getWidgetsView(mPopupContainerWithArrow.getWidgetContainer());
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index bd3778a..49d97d2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -187,7 +187,10 @@
/**
* Returns true if we can show the container.
+ *
+ * @deprecated Left here since some dependent projects are using this method
*/
+ @Deprecated
public static boolean canShow(View icon, ItemInfo item) {
return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item);
}
@@ -204,7 +207,7 @@
return null;
}
ItemInfo item = (ItemInfo) icon.getTag();
- if (!canShow(icon, item)) {
+ if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
@@ -218,7 +221,7 @@
popupDataProvider.getShortcutCountForItem(item),
popupDataProvider.getNotificationKeysForItem(item),
launcher.getSupportedShortcuts()
- .map(s -> s.getShortcut(launcher, item))
+ .map(s -> s.getShortcut(launcher, item, icon))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
@@ -538,7 +541,7 @@
public static void dismissInvalidPopup(BaseDraggingActivity activity) {
PopupContainerWithArrow popup = getOpen(activity);
if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
- || !canShow(popup.mOriginalIcon, (ItemInfo) popup.mOriginalIcon.getTag()))) {
+ || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
popup.animateClose();
}
}
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 7c393ad..e5e2c35 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -46,8 +46,8 @@
private final RemoteAction mAction;
public RemoteActionShortcut(RemoteAction action,
- BaseDraggingActivity activity, ItemInfo itemInfo) {
- super(0, R.id.action_remote_action_shortcut, activity, itemInfo);
+ BaseDraggingActivity activity, ItemInfo itemInfo, View originalView) {
+ super(0, R.id.action_remote_action_shortcut, activity, itemInfo, originalView);
mAction = action;
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 08d3779..0e25984c 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -46,18 +46,21 @@
protected final T mTarget;
protected final ItemInfo mItemInfo;
+ protected final View mOriginalView;
/**
* Indicates if it's invokable or not through some disabled UI
*/
private boolean isEnabled = true;
- public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
+ public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
+ View originalView) {
mIconResId = iconResId;
mLabelResId = labelResId;
mAccessibilityActionId = labelResId;
mTarget = target;
mItemInfo = itemInfo;
+ mOriginalView = originalView;
}
public SystemShortcut(SystemShortcut<T> other) {
@@ -66,6 +69,7 @@
mAccessibilityActionId = other.mAccessibilityActionId;
mTarget = other.mTarget;
mItemInfo = other.mItemInfo;
+ mOriginalView = other.mOriginalView;
}
/**
@@ -107,10 +111,10 @@
public interface Factory<T extends Context & ActivityContext> {
- @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
+ @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo, View originalView);
}
- public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> {
+ public static final Factory<Launcher> WIDGETS = (launcher, itemInfo, originalView) -> {
if (itemInfo.getTargetComponent() == null) return null;
final List<WidgetItem> widgets =
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
@@ -118,12 +122,13 @@
if (widgets.isEmpty()) {
return null;
}
- return new Widgets(launcher, itemInfo);
+ return new Widgets(launcher, itemInfo, originalView);
};
public static class Widgets extends SystemShortcut<Launcher> {
- public Widgets(Launcher target, ItemInfo itemInfo) {
- super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
+ public Widgets(Launcher target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo,
+ originalView);
}
@Override
@@ -145,9 +150,9 @@
@Nullable
private SplitAccessibilityInfo mSplitA11yInfo;
- public AppInfo(T target, ItemInfo itemInfo) {
+ public AppInfo(T target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
- itemInfo);
+ itemInfo, originalView);
}
/**
@@ -160,8 +165,9 @@
* That way it could directly create the correct node info for any shortcut that supports
* split, but then we'll need custom resIDs for each pair of shortcuts.
*/
- public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) {
- this(target, itemInfo);
+ public AppInfo(T target, ItemInfo itemInfo, View originalView,
+ SplitAccessibilityInfo accessibilityInfo) {
+ this(target, itemInfo, originalView);
mSplitA11yInfo = accessibilityInfo;
mAccessibilityActionId = accessibilityInfo.nodeId;
}
@@ -203,28 +209,29 @@
}
}
- public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
- boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
- && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
+ public static final Factory<BaseDraggingActivity> INSTALL =
+ (activity, itemInfo, originalView) -> {
+ boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
+ && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
- boolean isInstantApp = false;
- if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) {
- com.android.launcher3.model.data.AppInfo
- appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo;
- isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
- }
- boolean enabled = supportsWebUI || isInstantApp;
- if (!enabled) {
- return null;
- }
- return new Install(activity, itemInfo);
+ boolean isInstantApp = false;
+ if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) {
+ com.android.launcher3.model.data.AppInfo
+ appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo;
+ isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
+ }
+ boolean enabled = supportsWebUI || isInstantApp;
+ if (!enabled) {
+ return null;
+ }
+ return new Install(activity, itemInfo, originalView);
};
public static class Install extends SystemShortcut<BaseDraggingActivity> {
- public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
+ public Install(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
- target, itemInfo);
+ target, itemInfo, originalView);
}
@Override
diff --git a/src/com/android/launcher3/search/SearchCallback.java b/src/com/android/launcher3/search/SearchCallback.java
index 5796116..495a303 100644
--- a/src/com/android/launcher3/search/SearchCallback.java
+++ b/src/com/android/launcher3/search/SearchCallback.java
@@ -32,13 +32,6 @@
void onSearchResult(String query, ArrayList<T> items);
/**
- * Called when the search from secondary source is complete.
- *
- * @param items list of search results
- */
- void onAppendSearchResult(String query, ArrayList<T> items);
-
- /**
* Called when the search results should be cleared.
*/
void clearSearchResult();
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index e9058c3..a0ed77e 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -205,8 +205,8 @@
/**
* Returns a system shortcut to pin/unpin a shortcut
*/
- public SystemShortcut getSystemShortcut(ItemInfo info) {
- return new PinUnPinShortcut(mLauncher, info,
+ public SystemShortcut getSystemShortcut(ItemInfo info, View originalView) {
+ return new PinUnPinShortcut(mLauncher, info, originalView,
mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
}
@@ -214,10 +214,11 @@
private final boolean mIsPinned;
- PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+ PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, View originalView,
+ boolean isPinned) {
super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
- target, info);
+ target, info, originalView);
mIsPinned = isPinned;
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 9201006..c79d70d 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -120,23 +120,20 @@
int maxWidth =
grid.allAppsCellWidthPx * grid.numShownAllAppsColumns + horizontalPadding;
- int appsWidth = Math.min(width, maxWidth);
+ int appsWidth = Math.min(width - getPaddingLeft() - getPaddingRight(), maxWidth);
int maxHeight =
grid.allAppsCellHeightPx * grid.numShownAllAppsColumns + verticalPadding;
- int appsHeight = Math.min(height, maxHeight);
+ int appsHeight = Math.min(height - getPaddingTop() - getPaddingBottom(), maxHeight);
mAppsView.measure(
makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
-
} else if (child == mAllAppsButton) {
int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY);
mAllAppsButton.measure(appsButtonSpec, appsButtonSpec);
-
} else if (child == mWorkspace) {
measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec,
grid.iconSizePx + grid.edgeMarginPx);
-
} else {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
@@ -193,8 +190,8 @@
container.populateAndShow((BubbleTextView) v,
popupDataProvider.getShortcutCountForItem(item),
Collections.emptyList(),
- Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
- APP_INFO.getShortcut(mActivity, item)));
+ Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item, v),
+ APP_INFO.getShortcut(mActivity, item, v)));
v.getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 0c6d675..9af68c0 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -57,11 +57,17 @@
}
public static Intent makeIntent(ShortcutInfo si) {
+ return makeIntent(si.getId(), si.getPackage()).setComponent(si.getActivity());
+ }
+
+ /**
+ * Creates an intent for shortcut id and package name.
+ */
+ public static Intent makeIntent(String shortcutId, String packageName) {
return new Intent(Intent.ACTION_MAIN)
.addCategory(INTENT_CATEGORY)
- .setComponent(si.getActivity())
- .setPackage(si.getPackage())
+ .setPackage(packageName)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- .putExtra(EXTRA_SHORTCUT_ID, si.getId());
+ .putExtra(EXTRA_SHORTCUT_ID, shortcutId);
}
}
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index 122573c..f9a36ad 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -18,6 +18,7 @@
import android.content.Context;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
/**
* Interface representing a state of a StatefulActivity
@@ -36,7 +37,8 @@
/**
* @return How long the animation to this state should take (or from this state to NORMAL).
*/
- int getTransitionDuration(Context context);
+ <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
+ int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState);
/**
* Returns the state to go back to from this state
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 1767939..2aa9dde 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -253,8 +253,8 @@
// Since state mBaseState can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
mConfig.duration = state == mBaseState
- ? fromState.getTransitionDuration(mActivity)
- : state.getTransitionDuration(mActivity);
+ ? fromState.getTransitionDuration(mActivity, false /* isToState */)
+ : state.getTransitionDuration(mActivity, true /* isToState */);
prepareForAtomicAnimation(fromState, state, mConfig);
AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
if (listener != null) {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 8b52016..4cfced8 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -43,7 +43,7 @@
}
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
return 80;
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 8b425da..38b62d4 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -109,7 +109,7 @@
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (mDestroyed) return;
+ if (mDestroyed || mIgnoreAutoRotateSettings) return;
boolean wasRotationEnabled = mHomeRotationEnabled;
mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
getAllowRotationDefaultValue(mActivity.getDeviceProfile()));
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 52356ce..a205ab5 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -39,7 +39,7 @@
}
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
return 150;
}
@@ -51,9 +51,8 @@
return super.getWorkspaceScaleAndTranslation(launcher);
}
- float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
- float shrunkBottom = grid.getWorkspaceSpringLoadShrunkBottom();
- float scale = (shrunkBottom - shrunkTop) / ws.getNormalChildHeight();
+ float shrunkTop = grid.getCellLayoutSpringLoadShrunkTop();
+ float scale = grid.getWorkspaceSpringLoadScale();
float halfHeight = ws.getHeight() / 2;
float myCenter = ws.getTop() + halfHeight;
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index f04e685..f99519d 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -53,6 +53,7 @@
ANIM_WORKSPACE_FADE,
ANIM_HOTSEAT_SCALE,
ANIM_HOTSEAT_TRANSLATE,
+ ANIM_HOTSEAT_FADE,
ANIM_OVERVIEW_SCALE,
ANIM_OVERVIEW_TRANSLATE_X,
ANIM_OVERVIEW_TRANSLATE_Y,
@@ -72,6 +73,7 @@
public static final int ANIM_WORKSPACE_FADE = 3;
public static final int ANIM_HOTSEAT_SCALE = 4;
public static final int ANIM_HOTSEAT_TRANSLATE = 5;
+ public static final int ANIM_HOTSEAT_FADE = 16;
public static final int ANIM_OVERVIEW_SCALE = 6;
public static final int ANIM_OVERVIEW_TRANSLATE_X = 7;
public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
@@ -83,7 +85,7 @@
public static final int ANIM_OVERVIEW_ACTIONS_FADE = 14;
public static final int ANIM_WORKSPACE_PAGE_TRANSLATE_X = 15;
- private static final int ANIM_TYPES_COUNT = 16;
+ private static final int ANIM_TYPES_COUNT = 17;
protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index a6b481a..242d2d4 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -198,6 +198,12 @@
return new Point(cellRect.centerX(), cellRect.centerY());
});
+ case TestProtocol.REQUEST_HAS_TIS: {
+ response.putBoolean(
+ TestProtocol.REQUEST_HAS_TIS, false);
+ return response;
+ }
+
default:
return null;
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index e8fd2ff..3a030a8 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -110,6 +110,7 @@
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
"get-activities-created-count";
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
+ public static final String REQUEST_HAS_TIS = "has-touch-interaction-service";
public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a125fbe..09b8228 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -330,9 +331,6 @@
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
- if (targetState != mStartState) {
- logReachedState(targetState);
- }
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(startProgress, endProgress);
@@ -361,6 +359,8 @@
boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
if (shouldGoToTargetState) {
goToTargetState(targetState);
+ } else {
+ logReachedState(mToState);
}
}
@@ -368,13 +368,19 @@
if (!mLauncher.isInState(targetState)) {
// If we're already in the target state, don't jump to it at the end of the animation in
// case the user started interacting with it before the animation finished.
- mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ mLauncher.getStateManager().goToState(targetState, false /* animated */,
+ forEndCallback(() -> logReachedState(targetState)));
+ } else {
+ logReachedState(targetState);
}
mLauncher.getRootView().getSysUiScrim().createSysuiMultiplierAnim(
1f).setDuration(0).start();
}
private void logReachedState(LauncherState targetState) {
+ if (mStartState == targetState) {
+ return;
+ }
// Transition complete. log the action
mLauncher.getStatsLogManager().logger()
.withSrcState(mStartState.statsLogOrdinal)
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 5aac3f3..9d7fd9a 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -17,11 +17,19 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
@@ -39,11 +47,41 @@
private static final float ALLAPPS_STAGGERED_FADE_THRESHOLD = 0.5f;
+ // Custom timing for NORMAL -> ALL_APPS on phones only.
+ private static final float WORKSPACE_MOTION_START = 0.1667f;
+ private static final float ALL_APPS_STATE_TRANSITION = 0.305f;
+ private static final float ALL_APPS_FADE_END = 0.4717f;
+
public static final Interpolator ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER =
Interpolators.clampToProgress(LINEAR, 0, ALLAPPS_STAGGERED_FADE_THRESHOLD);
public static final Interpolator ALLAPPS_STAGGERED_FADE_LATE_RESPONDER =
Interpolators.clampToProgress(LINEAR, ALLAPPS_STAGGERED_FADE_THRESHOLD, 1f);
+ // Custom interpolators for NORMAL -> ALL_APPS on phones only.
+ public static final Interpolator BLUR =
+ Interpolators.clampToProgress(
+ EMPHASIZED_DECELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
+ public static final Interpolator WORKSPACE_SCALE =
+ Interpolators.clampToProgress(
+ EMPHASIZED_DECELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
+ public static final Interpolator HOTSEAT_FADE =
+ Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION);
+ public static final Interpolator HOTSEAT_TRANSLATE =
+ Interpolators.clampToProgress(
+ EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
+ public static final Interpolator SCRIM_FADE =
+ Interpolators.clampToProgress(
+ Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
+ WORKSPACE_MOTION_START, ALL_APPS_STATE_TRANSITION);
+ public static final Interpolator ALL_APPS_FADE =
+ Interpolators.clampToProgress(
+ Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1.0f),
+ ALL_APPS_STATE_TRANSITION, ALL_APPS_FADE_END);
+ public static final Interpolator ALL_APPS_VERTICAL_PROGRESS =
+ Interpolators.clampToProgress(
+ Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1.0f),
+ ALL_APPS_STATE_TRANSITION, 1.0f);
+
public AllAppsSwipeController(Launcher l) {
super(l, SingleAxisSwipeDetector.VERTICAL);
}
@@ -118,11 +156,17 @@
*/
public static void applyNormalToAllAppsAnimConfig(Launcher launcher,
StateAnimationConfig config) {
- boolean isTablet = launcher.getDeviceProfile().isTablet;
- config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
- config.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
- ? INSTANT : ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
+ if (launcher.getDeviceProfile().isTablet) {
+ config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
+ } else {
+ config.setInterpolator(ANIM_DEPTH, BLUR);
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, WORKSPACE_SCALE);
+ config.setInterpolator(ANIM_HOTSEAT_FADE, HOTSEAT_FADE);
+ config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, HOTSEAT_TRANSLATE);
+ config.setInterpolator(ANIM_SCRIM_FADE, SCRIM_FADE);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_FADE);
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS, ALL_APPS_VERTICAL_PROGRESS);
+ }
}
-
-
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8d57d69..c4d2bc4 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -43,6 +43,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.folder.Folder;
@@ -58,8 +59,10 @@
import com.android.launcher3.model.data.SearchActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -67,6 +70,8 @@
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetManagerHelper;
+import java.util.Collections;
+
/**
* Class for handling clicks on workspace and all-apps items
*/
@@ -95,8 +100,7 @@
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) {
- startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher
- );
+ startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
@@ -171,7 +175,8 @@
(d, i) -> startMarketIntentForPackage(v, launcher, packageName))
.setNeutralButton(R.string.abandoned_clean_this,
(d, i) -> launcher.getWorkspace()
- .removeAbandonedPromise(packageName, user))
+ .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
+ Collections.singleton(packageName), user)))
.create().show();
}
@@ -205,6 +210,12 @@
public static boolean handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) {
final int disabledFlags = shortcut.runtimeStatusFlags
& WorkspaceItemInfo.FLAG_DISABLED_MASK;
+ // Handle the case where the disabled reason is DISABLED_REASON_VERSION_LOWER.
+ // Show an AlertDialog for the user to choose either updating the app or cancel the launch.
+ if (maybeCreateAlertDialogForShortcut(shortcut, context)) {
+ return true;
+ }
+
if ((disabledFlags
& ~FLAG_DISABLED_SUSPENDED
& ~FLAG_DISABLED_QUIET_USER) == 0) {
@@ -230,6 +241,37 @@
}
}
+ private static boolean maybeCreateAlertDialogForShortcut(final WorkspaceItemInfo shortcut,
+ Context context) {
+ try {
+ final Launcher launcher = Launcher.getLauncher(context);
+ if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && shortcut.isDisabledVersionLower()) {
+
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.dialog_update_title)
+ .setMessage(R.string.dialog_update_message)
+ .setPositiveButton(R.string.dialog_update, (d, i) -> {
+ // Direct the user to the play store to update the app
+ context.startActivity(shortcut.getMarketIntent(context));
+ })
+ .setNeutralButton(R.string.dialog_remove, (d, i) -> {
+ // Remove the icon if launcher is successfully initialized
+ launcher.getWorkspace().persistRemoveItemsByMatcher(ItemInfoMatcher
+ .ofShortcutKeys(Collections.singleton(ShortcutKey
+ .fromItemInfo(shortcut))));
+ })
+ .create()
+ .show();
+ return true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error creating alert dialog", e);
+ }
+
+ return false;
+ }
+
/**
* Event handler for an app shortcut click.
*
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index f876dd9..6bae745 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -37,6 +37,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.views.BubbleTextHolder;
/**
* Class to handle long-clicks on workspace items and start drag as a result.
@@ -79,9 +80,12 @@
launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
}
- private static boolean onAllAppsItemLongClick(View v) {
+ private static boolean onAllAppsItemLongClick(View view) {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onAllAppsItemLongClick");
- v.cancelLongPress();
+ view.cancelLongPress();
+ View v = (view instanceof BubbleTextHolder)
+ ? ((BubbleTextHolder) view).getBubbleText()
+ : view;
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!canStartDrag(launcher)) return false;
// When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8005181..7c73be5 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -242,7 +242,9 @@
change |= CHANGE_SUPPORTED_BOUNDS;
Point currentS = newInfo.currentSize;
- Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size;
+ Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
+ oldInfo.mPerDisplayBounds.get(newInfo.displayId);
+ Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
Log.e("b/198965093",
"Inconsistent number of displays"
@@ -250,10 +252,12 @@
+ "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
+ "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
}
- if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
+ if (expectedS != null
+ && (Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
|| Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
&& display.getState() == Display.STATE_OFF) {
- Log.e("b/198965093", "Display size changed while display is off, ignoring change");
+ Log.e("b/198965093",
+ "Display size changed while display is off, ignoring change");
return;
}
}
@@ -286,7 +290,7 @@
// Configuration property
public final float fontScale;
- public final int densityDpi;
+ private final int densityDpi;
public final NavigationMode navigationMode;
private final PortraitSize mScreenSizeDp;
@@ -353,6 +357,10 @@
public float smallestSizeDp(WindowBounds bounds) {
return dpiFromPx(Math.min(bounds.bounds.width(), bounds.bounds.height()), densityDpi);
}
+
+ public int getDensityDpi() {
+ return densityDpi;
+ }
}
/**
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 8485371..6978e0c 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -15,17 +15,12 @@
*/
package com.android.launcher3.util;
-import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
-
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -42,7 +37,7 @@
private static final int KEEP_ALIVE = 1;
/** Dedicated executor instances for work depending on other packages. */
- private static final Map<String, ExecutorService> PACKAGE_EXECUTORS = new ConcurrentHashMap<>();
+ private static final Map<String, LooperExecutor> PACKAGE_EXECUTORS = new ConcurrentHashMap<>();
/**
* An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size.
@@ -90,11 +85,10 @@
*
* @param packageName Package associated with the executor.
*/
- public static ExecutorService getPackageExecutor(String packageName) {
+ public static LooperExecutor getPackageExecutor(String packageName) {
return PACKAGE_EXECUTORS.computeIfAbsent(
- packageName,
- p -> newSingleThreadExecutor(
- new SimpleThreadFactory(p, THREAD_PRIORITY_BACKGROUND)));
+ packageName, p -> new LooperExecutor(
+ createAndStartNewLooper(p, Process.THREAD_PRIORITY_DEFAULT)));
}
/**
diff --git a/src/com/android/launcher3/util/GridOccupancy.java b/src/com/android/launcher3/util/GridOccupancy.java
index 9c752a7..1301460 100644
--- a/src/com/android/launcher3/util/GridOccupancy.java
+++ b/src/com/android/launcher3/util/GridOccupancy.java
@@ -7,7 +7,7 @@
/**
* Utility object to manage the occupancy in a grid.
*/
-public class GridOccupancy {
+public class GridOccupancy extends AbsGridOccupancy {
private final int mCountX;
private final int mCountY;
@@ -30,24 +30,7 @@
* @return true if a vacant cell was found
*/
public boolean findVacantCell(int[] vacantOut, int spanX, int spanY) {
- for (int y = 0; (y + spanY) <= mCountY; y++) {
- for (int x = 0; (x + spanX) <= mCountX; x++) {
- boolean available = !cells[x][y];
- out:
- for (int i = x; i < x + spanX; i++) {
- for (int j = y; j < y + spanY; j++) {
- available = available && !cells[i][j];
- if (!available) break out;
- }
- }
- if (available) {
- vacantOut[0] = x;
- vacantOut[1] = y;
- return true;
- }
- }
- }
- return false;
+ return super.findVacantCell(vacantOut, cells, mCountX, mCountY, spanX, spanY);
}
public void copyTo(GridOccupancy dest) {
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 7917410..b6af314 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -19,6 +19,8 @@
import android.content.ComponentName;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -27,90 +29,64 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Predicate;
/**
* A utility class to check for {@link ItemInfo}
*/
-public interface ItemInfoMatcher {
+public abstract class ItemInfoMatcher {
/**
* Empty component used for match testing
*/
- ComponentName EMPTY_COMPONENT = new ComponentName("", "");
+ private static final ComponentName EMPTY_COMPONENT = new ComponentName("", "");
- boolean matches(ItemInfo info, ComponentName cn);
-
- /**
- * Returns true if the itemInfo matches this check
- */
- default boolean matchesInfo(ItemInfo info) {
- if (info != null) {
- ComponentName cn = info.getTargetComponent();
- return matches(info, cn != null ? cn : EMPTY_COMPONENT);
- } else {
- return false;
- }
+ public static Predicate<ItemInfo> ofUser(UserHandle user) {
+ return info -> info != null && info.user.equals(user);
}
- /**
- * Returns a new matcher with returns true if either this or {@param matcher} returns true.
- */
- default ItemInfoMatcher or(ItemInfoMatcher matcher) {
- return (info, cn) -> matches(info, cn) || matcher.matches(info, cn);
+ public static Predicate<ItemInfo> ofComponents(
+ HashSet<ComponentName> components, UserHandle user) {
+ return info -> info != null && info.user.equals(user)
+ && components.contains(getNonNullComponent(info));
}
- /**
- * Returns a new matcher with returns true if both this and {@param matcher} returns true.
- */
- default ItemInfoMatcher and(ItemInfoMatcher matcher) {
- return (info, cn) -> matches(info, cn) && matcher.matches(info, cn);
+ public static Predicate<ItemInfo> ofPackages(Set<String> packageNames, UserHandle user) {
+ return info -> info != null && info.user.equals(user)
+ && packageNames.contains(getNonNullComponent(info).getPackageName());
}
- /**
- * Returns a new matcher with returns the opposite value of this.
- */
- default ItemInfoMatcher negate() {
- return (info, cn) -> !matches(info, cn);
- }
-
- static ItemInfoMatcher ofUser(UserHandle user) {
- return (info, cn) -> info.user.equals(user);
- }
-
- static ItemInfoMatcher ofComponents(HashSet<ComponentName> components, UserHandle user) {
- return (info, cn) -> components.contains(cn) && info.user.equals(user);
- }
-
- static ItemInfoMatcher ofPackages(Set<String> packageNames, UserHandle user) {
- return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
- }
-
- static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
- return (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ public static Predicate<ItemInfo> ofShortcutKeys(Set<ShortcutKey> keys) {
+ return info -> info != null && info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& keys.contains(ShortcutKey.fromItemInfo(info));
}
/**
* Returns a matcher for items within folders.
*/
- static ItemInfoMatcher forFolderMatch(ItemInfoMatcher childOperator) {
- return (info, cn) -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
- .anyMatch(childOperator::matchesInfo);
+ public static Predicate<ItemInfo> forFolderMatch(Predicate<ItemInfo> childOperator) {
+ return info -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+ .anyMatch(childOperator);
}
/**
* Returns a matcher for items with provided ids
*/
- static ItemInfoMatcher ofItemIds(IntSet ids) {
- return (info, cn) -> ids.contains(info.id);
+ public static Predicate<ItemInfo> ofItemIds(IntSet ids) {
+ return info -> info != null && ids.contains(info.id);
}
/**
* Returns a matcher for items with provided items
*/
- static ItemInfoMatcher ofItems(Collection<? extends ItemInfo> items) {
+ public static Predicate<ItemInfo> ofItems(Collection<? extends ItemInfo> items) {
IntSet ids = new IntSet();
items.forEach(item -> ids.add(item.id));
return ofItemIds(ids);
}
+
+ private static ComponentName getNonNullComponent(@NonNull ItemInfo info) {
+ ComponentName cn = info.getTargetComponent();
+ return cn != null ? cn : EMPTY_COMPONENT;
+ }
}
diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
new file mode 100644
index 0000000..50f7027
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.util.ArrayMap;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+
+/**
+ * Allows to combine multiple values set by several sources.
+ *
+ * The various sources are meant to use [set], providing different `setterIndex` params. When it is
+ * not set, 0 is used. This is meant to cover the case multiple animations are going on at the same
+ * time.
+ *
+ * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
+ * It sets the addition of all values.
+ *
+ * @param <T> Type where to apply the property.
+ */
+public class MultiAdditivePropertyFactory<T extends View> {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "MultiAdditivePropertyFactory";
+ private final String mName;
+ private final ArrayMap<Integer, MultiAdditiveProperty> mProperties =
+ new ArrayMap<>();
+
+ // This is an optimization for cases when set is called repeatedly with the same setterIndex.
+ private float mAggregationOfOthers = 0f;
+ private Integer mLastIndexSet = -1;
+ private final Property<View, Float> mProperty;
+
+ public MultiAdditivePropertyFactory(String name, Property<View, Float> property) {
+ mName = name;
+ mProperty = property;
+ }
+
+ /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
+ public MultiAdditiveProperty get(Integer index) {
+ return mProperties.computeIfAbsent(index,
+ (k) -> new MultiAdditiveProperty(index, mName + "_" + index));
+ }
+
+ /**
+ * Each [setValue] will be aggregated with the other properties values created by the
+ * corresponding factory.
+ */
+ class MultiAdditiveProperty extends FloatProperty<T> {
+ private final int mInx;
+ private float mValue = 0f;
+
+ MultiAdditiveProperty(int inx, String name) {
+ super(name);
+ mInx = inx;
+ }
+
+ @Override
+ public void setValue(T obj, float newValue) {
+ if (mLastIndexSet != mInx) {
+ mAggregationOfOthers = 0f;
+ mProperties.forEach((key, property) -> {
+ if (key != mInx) {
+ mAggregationOfOthers += property.mValue;
+ }
+ });
+ mLastIndexSet = mInx;
+ }
+ float lastAggregatedValue = mAggregationOfOthers + newValue;
+ mValue = newValue;
+ apply(obj, lastAggregatedValue);
+
+ if (DEBUG) {
+ Log.d(TAG, "name=" + mName
+ + " newValue=" + newValue + " mInx=" + mInx
+ + " aggregated=" + lastAggregatedValue + " others= " + mProperties);
+ }
+ }
+
+ @Override
+ public Float get(T view) {
+ // The scale of the view should match mLastAggregatedValue. Still, if it has been
+ // changed without using this property, it can differ. As this get method is usually
+ // used to set the starting point on an animation, this would result in some jumps
+ // when the view scale is different than the last aggregated value. To stay on the
+ // safe side, let's return the real view scale.
+ return mProperty.get(view);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mValue);
+ }
+ }
+
+ protected void apply(View view, float value) {
+ mProperty.set(view, value);
+ }
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 39d7cfe..c1e4fa8 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -38,8 +38,9 @@
public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
- public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
+ public static final String SEARCH_KEYBOARD_EDU_SEEN = "launcher.search_edu_seen";
public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
+ public static final String SEARCH_ONBOARDING_COUNT = "launcher.search_onboarding_count";
public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
// When adding a new key, add it here as well, to be able to reset it from Developer Options.
@@ -47,7 +48,8 @@
"All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
"Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
HOTSEAT_LONGPRESS_TIP_SEEN },
- "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+ "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
+ SEARCH_ONBOARDING_COUNT},
"Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
"All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
);
@@ -58,12 +60,11 @@
@StringDef(value = {
HOME_BOUNCE_SEEN,
HOTSEAT_LONGPRESS_TIP_SEEN,
- SEARCH_EDU_SEEN,
+ SEARCH_KEYBOARD_EDU_SEEN,
TASKBAR_EDU_SEEN
})
@Retention(RetentionPolicy.SOURCE)
- public @interface EventBoolKey {
- }
+ public @interface EventBoolKey {}
/**
* Events that occur multiple times, which we count up to a max defined in {@link #MAX_COUNTS}.
@@ -72,19 +73,21 @@
HOME_BOUNCE_COUNT,
HOTSEAT_DISCOVERY_TIP_COUNT,
SEARCH_SNACKBAR_COUNT,
+ SEARCH_ONBOARDING_COUNT,
ALL_APPS_VISITED_COUNT
})
@Retention(RetentionPolicy.SOURCE)
- public @interface EventCountKey {
- }
+ public @interface EventCountKey {}
private static final Map<String, Integer> MAX_COUNTS;
static {
- Map<String, Integer> maxCounts = new ArrayMap<>(4);
+ Map<String, Integer> maxCounts = new ArrayMap<>(5);
maxCounts.put(HOME_BOUNCE_COUNT, 3);
maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
maxCounts.put(SEARCH_SNACKBAR_COUNT, 3);
+ // This is the sum of all onboarding cards. Currently there is only 1 card shown 3 times.
+ maxCounts.put(SEARCH_ONBOARDING_COUNT, 3);
maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 5aaa275..92f718e 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -22,7 +22,9 @@
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
-import static com.android.launcher3.ResourceUtils.getDimenByName;
+import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT;
+import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import static com.android.launcher3.util.RotationUtils.deltaRotation;
@@ -153,20 +155,26 @@
boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
boolean isGesture = isGestureNav(context);
+ boolean isPortrait = config.screenHeightDp > config.screenWidthDp;
int bottomNav = isTablet
? 0
- : (config.screenHeightDp > config.screenWidthDp
- ? getDimenByName(NAVBAR_HEIGHT, systemRes, 0)
+ : (isPortrait
+ ? getDimenByName(systemRes, NAVBAR_HEIGHT)
: (isGesture
- ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0)
+ ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE)
: 0));
Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
- int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+
+
+ int statusBarHeight = getDimenByName(systemRes,
+ (isPortrait) ? STATUS_BAR_HEIGHT_PORTRAIT : STATUS_BAR_HEIGHT_LANDSCAPE,
+ STATUS_BAR_HEIGHT);
+
Insets newStatusBarInsets = Insets.of(
statusBarInsets.left,
Math.max(statusBarInsets.top, statusBarHeight),
@@ -222,23 +230,26 @@
boolean isTabletOrGesture = isTablet
|| (Utilities.ATLEAST_R && isGestureNav(context));
- int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+ int statusBarHeightPortrait = getDimenByName(systemRes,
+ STATUS_BAR_HEIGHT_PORTRAIT, STATUS_BAR_HEIGHT);
+ int statusBarHeightLandscape = getDimenByName(systemRes,
+ STATUS_BAR_HEIGHT_LANDSCAPE, STATUS_BAR_HEIGHT);
int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
navBarHeightPortrait = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
- : getDimenByName(NAVBAR_HEIGHT, systemRes, 0);
+ : getDimenByName(systemRes, NAVBAR_HEIGHT);
navBarHeightLandscape = isTablet
? (mTaskbarDrawnInProcess
? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
: (isTabletOrGesture
- ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0);
+ ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
navbarWidthLandscape = isTabletOrGesture
? 0
- : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0);
+ : getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
WindowBounds[] result = new WindowBounds[4];
Point tempSize = new Point();
@@ -248,13 +259,15 @@
rotateSize(tempSize, rotationChange);
Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);
- int navBarHeight, navbarWidth;
+ int navBarHeight, navbarWidth, statusBarHeight;
if (tempSize.y > tempSize.x) {
navBarHeight = navBarHeightPortrait;
navbarWidth = 0;
+ statusBarHeight = statusBarHeightPortrait;
} else {
navBarHeight = navBarHeightLandscape;
navbarWidth = navbarWidthLandscape;
+ statusBarHeight = statusBarHeightLandscape;
}
Rect insets = new Rect(safeCutout);
@@ -274,6 +287,21 @@
return result;
}
+ /**
+ * Wrapper around the utility method for easier emulation
+ */
+ protected int getDimenByName(Resources res, String resName) {
+ return ResourceUtils.getDimenByName(resName, res, 0);
+ }
+
+ /**
+ * Wrapper around the utility method for easier emulation
+ */
+ protected int getDimenByName(Resources res, String resName, String fallback) {
+ int dimen = ResourceUtils.getDimenByName(resName, res, -1);
+ return dimen > -1 ? dimen : getDimenByName(res, fallback);
+ }
+
protected boolean isGestureNav(Context context) {
return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index ed31e8d..47503b1 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -231,13 +231,17 @@
if (mSwipeDetector.isIdleState()) {
mOpenCloseAnimator
.setDuration(defaultDuration)
- .setInterpolator(Interpolators.ACCEL);
+ .setInterpolator(getIdleInterpolator());
} else {
mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
}
mOpenCloseAnimator.start();
}
+ protected Interpolator getIdleInterpolator() {
+ return Interpolators.ACCEL;
+ }
+
protected void onCloseComplete() {
mIsOpen = false;
getPopupContainer().removeView(this);
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f71aa13..4c001fd 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -413,6 +413,14 @@
}
/**
+ * Similar to {@link #mapCoordInSelfToDescendant(View descendant, float[] coord)}
+ * but accepts a Rect instead of float[].
+ */
+ public void mapRectInSelfToDescendant(View descendant, Rect rect) {
+ Utilities.mapRectInSelfToDescendant(descendant, this, rect);
+ }
+
+ /**
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
*/
public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java
index 76c465c..84f8049 100644
--- a/src/com/android/launcher3/views/BubbleTextHolder.java
+++ b/src/com/android/launcher3/views/BubbleTextHolder.java
@@ -16,8 +16,6 @@
package com.android.launcher3.views;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
/**
* Views that contain {@link BubbleTextView} should implement this interface.
@@ -25,14 +23,6 @@
public interface BubbleTextHolder extends IconLabelDotView {
BubbleTextView getBubbleText();
- /**
- * Called when new {@link ItemInfo} is set to {@link BubbleTextView}
- *
- * @param itemInfo the new itemInfo
- */
- default void onItemInfoUpdated(ItemInfoWithIcon itemInfo) {
- }
-
@Override
default void setIconVisible(boolean visible) {
getBubbleText().setIconVisible(visible);
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 56a1d37..babe607 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -49,7 +49,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -413,8 +412,7 @@
@WorkerThread
@SuppressWarnings("WrongThread")
private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
- if (!(drawable instanceof AdaptiveIconDrawable)
- || (drawable instanceof FolderAdaptiveIcon)) {
+ if (!(drawable instanceof AdaptiveIconDrawable)) {
return 0;
}
int blurSizeOutline =
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index a982786..7a8e9d5 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -20,6 +20,8 @@
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync;
+
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
@@ -283,6 +285,9 @@
}
break;
case MotionEvent.ACTION_UP:
+ hideKeyboardAsync(ActivityContext.lookupContext(getContext()),
+ getApplicationWindowToken());
+ break;
case MotionEvent.ACTION_CANCEL:
mRv.onFastScrollCompleted();
mTouchOffsetY = 0;
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index d2d569f..9442734 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.Utilities.ATLEAST_R;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN;
import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
@@ -106,7 +105,10 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
int widthUsed;
- if (mInsets.bottom > 0) {
+ if (deviceProfile.isTablet) {
+ int margin = deviceProfile.allAppsLeftRightMargin;
+ widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right));
+ } else if (mInsets.bottom > 0) {
widthUsed = mInsets.left + mInsets.right;
} else {
Rect padding = deviceProfile.workspacePadding;
@@ -114,18 +116,8 @@
2 * (mInsets.left + mInsets.right));
}
- if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
- // In large screen devices, we restrict the width of the widgets picker to show part of
- // the home screen. Let's ensure the minimum width used is at least the minimum width
- // that isn't taken by the widgets picker.
- int minUsedWidth = (int) (deviceProfile.availableWidthPx
- * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
- widthUsed = Math.max(widthUsed, minUsedWidth);
- }
-
- int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
- widthUsed, heightMeasureSpec, heightUsed);
+ widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index b12574f..1bcba14 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,6 +16,8 @@
package com.android.launcher3.widget;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -23,6 +25,7 @@
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
+import android.view.WindowInsets;
import android.widget.Toast;
import androidx.annotation.GuardedBy;
@@ -43,7 +46,9 @@
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.AbstractSlideInView;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ArrowTipView;
/**
@@ -54,11 +59,6 @@
PopupDataProvider.PopupDataChangeListener, Insettable {
/** The default number of cells that can fit horizontally in a widget sheet. */
protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
- /**
- * The maximum scale, [0, 1], of the device screen width that the widgets picker can consume
- * on large screen devices.
- */
- protected static final float MAX_WIDTH_SCALE_FOR_LARGER_SCREEN = 0.89f;
protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
"launcher.widgets_education_tip_seen";
@@ -69,10 +69,15 @@
private int mContentHorizontalMarginInPx;
+ protected int mNavBarScrimHeight;
+ private final Paint mNavBarScrimPaint;
+
public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContentHorizontalMarginInPx = getResources().getDimensionPixelSize(
R.dimen.widget_list_horizontal_margin);
+ mNavBarScrimPaint = new Paint();
+ mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
}
protected int getScrimColor(Context context) {
@@ -82,6 +87,9 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ WindowInsets windowInsets = WindowManagerProxy.INSTANCE.get(getContext())
+ .normalizeWindowInsets(getContext(), getRootWindowInsets(), new Rect());
+ mNavBarScrimHeight = getNavBarScrimHeight(windowInsets);
mActivityContext.getPopupDataProvider().setChangeListener(this);
}
@@ -135,6 +143,30 @@
}
}
+ private int getNavBarScrimHeight(WindowInsets insets) {
+ if (Utilities.ATLEAST_Q) {
+ return insets.getTappableElementInsets().bottom;
+ } else {
+ return insets.getStableInsetBottom();
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mNavBarScrimHeight = getNavBarScrimHeight(insets);
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mNavBarScrimHeight > 0) {
+ canvas.drawRect(0, getHeight() - mNavBarScrimHeight, getWidth(), getHeight(),
+ mNavBarScrimPaint);
+ }
+ }
+
/** Called when the horizontal margin of the content view has changed. */
protected abstract void onContentHorizontalMarginChanged(int contentHorizontalMarginInPx);
@@ -146,7 +178,10 @@
protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
int widthUsed;
- if (mInsets.bottom > 0) {
+ if (deviceProfile.isTablet) {
+ int margin = deviceProfile.allAppsLeftRightMargin;
+ widthUsed = Math.max(2 * margin, 2 * (mInsets.left + mInsets.right));
+ } else if (mInsets.bottom > 0) {
widthUsed = mInsets.left + mInsets.right;
} else {
Rect padding = deviceProfile.workspacePadding;
@@ -154,15 +189,6 @@
2 * (mInsets.left + mInsets.right));
}
- if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
- // In large screen devices, we restrict the width of the widgets picker to show part of
- // the home screen. Let's ensure the minimum width used is at least the minimum width
- // that isn't taken by the widgets picker.
- int minUsedWidth = (int) (deviceProfile.availableWidthPx
- * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
- widthUsed = Math.max(widthUsed, minUsedWidth);
- }
-
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
@@ -306,4 +332,11 @@
return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
|| Utilities.IS_RUNNING_IN_TEST_HARNESS;
}
+
+ @Override
+ protected void setTranslationShift(float translationShift) {
+ super.setTranslationShift(translationShift);
+ Launcher launcher = ActivityContext.lookupContext(getContext());
+ launcher.onWidgetsTransition(1 - translationShift);
+ }
}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 1f6551e..130ee3a 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -18,7 +18,6 @@
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
-import static com.android.launcher3.widget.WidgetSections.getWidgetSections;
import android.content.Context;
import android.graphics.Canvas;
@@ -341,8 +340,6 @@
if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
return null;
}
- Context context = getContext();
- return context.getDrawable(getWidgetSections(context).get(
- mInfo.pendingItemInfo.widgetCategory).mSectionDrawable);
+ return mInfo.pendingItemInfo.newIcon(getContext());
}
}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index b152ddc..bf521cc 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -247,10 +247,12 @@
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
+ int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
mContent.setPadding(mContent.getPaddingStart(),
- mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
- if (insets.bottom > 0) {
+ mContent.getPaddingTop(), mContent.getPaddingEnd(),
+ bottomPadding);
+ if (bottomPadding > 0) {
setupNavBarColor();
} else {
clearNavBarColor();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 341cb5c..a49cdc0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -328,15 +328,15 @@
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
-
- setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
- setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, insets.bottom);
+ int bottomPadding = Math.max(insets.bottom, mNavBarScrimHeight);
+ setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, bottomPadding);
+ setBottomPadding(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView, bottomPadding);
if (mHasWorkProfile) {
- setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
+ setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, bottomPadding);
}
- ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = insets.bottom;
+ ((MarginLayoutParams) mNoWidgetsView.getLayoutParams()).bottomMargin = bottomPadding;
- if (insets.bottom > 0) {
+ if (bottomPadding > 0) {
setupNavBarColor();
} else {
clearNavBarColor();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 932e06d..48df04f 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
import android.content.Context;
import android.content.res.Resources;
@@ -43,8 +42,6 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.PluralMessageFormat;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.WidgetSections;
-import com.android.launcher3.widget.WidgetSections.WidgetSection;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
@@ -98,7 +95,7 @@
mTitle = findViewById(R.id.app_title);
mSubtitle = findViewById(R.id.app_subtitle);
mExpandToggle = findViewById(R.id.toggle);
- findViewById(R.id.app_container).setAccessibilityDelegate(new AccessibilityDelegate() {
+ setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -177,13 +174,7 @@
private void setIcon(PackageItemInfo info) {
Drawable icon;
- if (info.widgetCategory == NO_CATEGORY) {
- icon = info.newIcon(getContext());
- } else {
- WidgetSection widgetSection = WidgetSections.getWidgetSections(getContext())
- .get(info.widgetCategory);
- icon = getContext().getDrawable(widgetSection.mSectionDrawable);
- }
+ icon = info.newIcon(getContext());
applyDrawables(icon);
mIconDrawable = icon;
if (mIconDrawable != null) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index f780f03..755e4a9 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -239,21 +239,6 @@
mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
}
- @Override
- public void scrollToTop() {
- if (mScrollbar != null) {
- mScrollbar.reattachThumbToScroll();
- }
-
- if (getLayoutManager() instanceof LinearLayoutManager) {
- if (getCurrentScrollY() == 0) {
- // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
- return;
- }
- }
- scrollToPosition(0);
- }
-
/**
* Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
* {@code untilIndex}.
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
index 2751a52..a15508a 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -95,11 +95,6 @@
}
@Override
- public void onAppendSearchResult(String query, ArrayList<WidgetsListBaseEntry> items) {
- // Not needed.
- }
-
- @Override
public void clearSearchResult() {
// Any existing search session will be cancelled by setting text to empty.
mInput.setText("");
diff --git a/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java b/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
new file mode 100644
index 0000000..968b281
--- /dev/null
+++ b/src_shortcuts_overrides/com/android/launcher3/util/AbsGridOccupancy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Defines method to find the next vacant cell on a grid.
+ * This uses the default top-down, left-right approach and can be over-written through
+ * code swaps in different launchers.
+ */
+public abstract class AbsGridOccupancy {
+ /**
+ * Find the first vacant cell, if there is one.
+ *
+ * @param vacantOut Holds the x and y coordinate of the vacant cell
+ * @param spanX Horizontal cell span.
+ * @param spanY Vertical cell span.
+ *
+ * @return true if a vacant cell was found
+ */
+ protected boolean findVacantCell(int[] vacantOut, boolean[][] cells, int countX, int countY,
+ int spanX, int spanY) {
+ for (int y = 0; (y + spanY) <= countY; y++) {
+ for (int x = 0; (x + spanX) <= countX; x++) {
+ boolean available = !cells[x][y];
+ out:
+ for (int i = x; i < x + spanX; i++) {
+ for (int j = y; j < y + spanY; j++) {
+ available = available && !cells[i][j];
+ if (!available) break out;
+ }
+ }
+ if (available) {
+ vacantOut[0] = x;
+ vacantOut[1] = y;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index 8a435c9..ec921e8 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -20,6 +20,7 @@
import android.content.Context;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -39,8 +40,11 @@
}
@Override
- public int getTransitionDuration(Context context) {
- return 320;
+ public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
+ int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ return !context.getDeviceProfile().isTablet && isToState
+ ? 600
+ : isToState ? 500 : 300;
}
@Override
@@ -55,15 +59,21 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation scaleAndTranslation =
- new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+ return new ScaleAndTranslation(0.97f, NO_OFFSET, NO_OFFSET);
+ }
+
+ @Override
+ public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if (launcher.getDeviceProfile().isTablet) {
- scaleAndTranslation.scale = 0.97f;
+ return getWorkspaceScaleAndTranslation(launcher);
} else {
- scaleAndTranslation.translationY =
- -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT;
+ ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
+ .getWorkspaceScaleAndTranslation(launcher);
+ return new ScaleAndTranslation(
+ NO_SCALE,
+ overviewScaleAndTranslation.translationX,
+ overviewScaleAndTranslation.translationY);
}
- return scaleAndTranslation;
}
@Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index d154317..7a228c4 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -34,7 +34,7 @@
}
@Override
- public int getTransitionDuration(Context context) {
+ public int getTransitionDuration(Context context, boolean isToState) {
return 250;
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 7542d04..54cded0 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -37,6 +37,7 @@
"src/com/android/launcher3/util/WidgetUtils.java",
"src/com/android/launcher3/util/rule/FailureWatcher.java",
"src/com/android/launcher3/util/rule/LauncherActivityRule.java",
+ "src/com/android/launcher3/util/rule/SamplerRule.java",
"src/com/android/launcher3/util/rule/ScreenRecordRule.java",
"src/com/android/launcher3/util/rule/ShellCommandRule.java",
"src/com/android/launcher3/util/rule/SimpleActivityRule.java",
diff --git a/tests/res/raw/devices.json b/tests/res/raw/devices.json
new file mode 100644
index 0000000..a78dd86
--- /dev/null
+++ b/tests/res/raw/devices.json
@@ -0,0 +1,45 @@
+{
+ "pixel6pro": {
+ "width": 1440,
+ "height": 3120,
+ "density": 560,
+ "name": "pixel6pro",
+ "cutout": "0, 130, 0, 0",
+ "grids": [
+ "normal",
+ "reasonable",
+ "practical",
+ "big",
+ "crazy_big"
+ ],
+ "resourceOverrides": {
+ "status_bar_height": 98,
+ "navigation_bar_height_landscape": 56,
+ "navigation_bar_height": 56,
+ "navigation_bar_width": 56
+ }
+ },
+ "test": {
+ "data needs updating": 0
+ },
+ "pixel5": {
+ "width": 1080,
+ "height": 2340,
+ "density": 440,
+ "name": "pixel5",
+ "cutout": "0, 136, 0, 0",
+ "grids": [
+ "normal",
+ "reasonable",
+ "practical",
+ "big",
+ "crazy_big"
+ ],
+ "resourceOverrides": {
+ "status_bar_height": 66,
+ "navigation_bar_height_landscape": 44,
+ "navigation_bar_height": 44,
+ "navigation_bar_width": 44
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index f91f1c4..6d0fcb6 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -64,6 +64,7 @@
windowBounds = WindowBounds(x, y, x, y - 100, 0)
whenever(info.isTablet(any())).thenReturn(false)
+ whenever(info.getDensityDpi()).thenReturn(560)
inv = newScalableInvariantDeviceProfile()
}
@@ -77,6 +78,7 @@
windowBounds = WindowBounds(x, y, x, y - 100, 0)
whenever(info.isTablet(any())).thenReturn(true)
+ whenever(info.getDensityDpi()).thenReturn(320)
inv = newScalableInvariantDeviceProfile()
}
@@ -107,6 +109,7 @@
PointF(16f, 16f)
).toTypedArray()
hotseatBorderSpaces = FloatArray(4) { 16f }
+ hotseatColumnSpan = IntArray(4) { 4 }
iconSize = FloatArray(4) { 56f }
allAppsIconSize = FloatArray(4) { 56f }
iconTextSize = FloatArray(4) { 14f }
diff --git a/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
new file mode 100644
index 0000000..80259a5
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3
+
+import android.graphics.PointF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile] grid dimensions.
+ *
+ * This includes workspace, cell layout, shortcut and widget container, cell sizes, etc.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileGridDimensionsTest : DeviceProfileBaseTest() {
+
+ @Test
+ fun getCellLayoutWidth_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+ val tabletWidth = 2560
+ val tabletHeight = 1600
+ val availableWidth = 2560
+ val availableHeight = 1500
+ windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+ useTwoPanels = true
+ whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+ whenever(info.densityDpi).thenReturn(320)
+ inv = newScalableInvariantDeviceProfile()
+
+ val dp = newDP()
+
+ val expectedWorkspaceWidth = availableWidth
+ val expectedCellLayoutWidth =
+ (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+ dp.panelCount
+ assertThat(dp.cellLayoutWidth).isEqualTo(expectedCellLayoutWidth)
+ }
+
+ @Test
+ fun getCellLayoutHeight_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelHeight() {
+ val tabletWidth = 2560
+ val tabletHeight = 1600
+ val availableWidth = 2560
+ val availableHeight = 1500
+ windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+ useTwoPanels = true
+ whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+ whenever(info.densityDpi).thenReturn(320)
+ inv = newScalableInvariantDeviceProfile()
+
+ val dp = newDP()
+
+ val expectedWorkspaceHeight = availableHeight
+ val expectedCellLayoutHeight =
+ expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+ assertThat(dp.cellLayoutHeight).isEqualTo(expectedCellLayoutHeight)
+ }
+
+ @Test
+ fun getCellSize_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+ val tabletWidth = 2560
+ val tabletHeight = 1600
+ val availableWidth = 2560
+ val availableHeight = 1500
+ windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+ useTwoPanels = true
+ whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+ whenever(info.densityDpi).thenReturn(320)
+ inv = newScalableInvariantDeviceProfile()
+
+ val dp = newDP()
+
+ val expectedWorkspaceWidth = availableWidth
+ val expectedCellLayoutWidth =
+ (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+ dp.panelCount
+ val expectedShortcutAndWidgetContainerWidth =
+ expectedCellLayoutWidth -
+ (dp.cellLayoutPaddingPx.left + dp.cellLayoutPaddingPx.right)
+ assertThat(dp.getCellSize().x).isEqualTo(
+ (expectedShortcutAndWidgetContainerWidth -
+ ((inv!!.numColumns - 1) * dp.cellLayoutBorderSpacePx.x)) / inv!!.numColumns)
+ val expectedWorkspaceHeight = availableHeight
+ val expectedCellLayoutHeight =
+ expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+ val expectedShortcutAndWidgetContainerHeight = expectedCellLayoutHeight -
+ (dp.cellLayoutPaddingPx.top + dp.cellLayoutPaddingPx.bottom)
+ assertThat(dp.getCellSize().y).isEqualTo(
+ (expectedShortcutAndWidgetContainerHeight -
+ ((inv!!.numRows - 1) * dp.cellLayoutBorderSpacePx.y)) / inv!!.numRows)
+ }
+
+ @Test
+ fun getPanelCount_twoPanelLandscapeScalable4By4GridTablet_equalsTwoPanels() {
+ val tabletWidth = 2560
+ val tabletHeight = 1600
+ val availableWidth = 2560
+ val availableHeight = 1500
+ windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+ useTwoPanels = true
+ whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+ whenever(info.densityDpi).thenReturn(320)
+ inv = newScalableInvariantDeviceProfile()
+
+ val dp = newDP()
+
+ assertThat(dp.panelCount).isEqualTo(2)
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/HotseatSizeTest.kt b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
similarity index 97%
rename from tests/src/com/android/launcher3/HotseatSizeTest.kt
rename to tests/src/com/android/launcher3/HotseatShownIconsTest.kt
index a44939f..593239d 100644
--- a/tests/src/com/android/launcher3/HotseatSizeTest.kt
+++ b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
@@ -23,15 +23,13 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.`when` as whenever
/**
* Test for [DeviceProfile]
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
-class HotseatSizeTest : DeviceProfileBaseTest() {
+class HotseatShownIconsTest : DeviceProfileBaseTest() {
@Test
fun hotseat_size_is_normal_for_handhelds() {
diff --git a/tests/src/com/android/launcher3/InlineQsbTest.kt b/tests/src/com/android/launcher3/InlineQsbTest.kt
index e00dca8..905c1e1 100644
--- a/tests/src/com/android/launcher3/InlineQsbTest.kt
+++ b/tests/src/com/android/launcher3/InlineQsbTest.kt
@@ -29,17 +29,16 @@
class InlineQsbTest : DeviceProfileBaseTest() {
@Test
- fun qsbWidth_is_match_parent_for_phones() {
+ fun qsb_is_not_inline_for_phones() {
initializeVarsForPhone()
val dp = newDP()
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
}
@Test
- fun qsbWidth_is_match_parent_for_tablet_portrait() {
+ fun qsb_is_inline_for_tablet_portrait() {
initializeVarsForTablet()
inv = newScalableInvariantDeviceProfile().apply {
inlineQsb = booleanArrayOf(
@@ -62,11 +61,10 @@
)
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
}
@Test
- fun qsbWidth_has_size_for_tablet_landscape() {
+ fun qsb_is_inline_for_tablet_landscape() {
initializeVarsForTablet(isLandscape = true)
inv = newScalableInvariantDeviceProfile().apply {
inlineQsb = booleanArrayOf(
@@ -75,16 +73,17 @@
false,
false
)
+ numColumns = 6
+ numRows = 5
+ numShownHotseatIcons = 6
}
val dp = newDP()
if (dp.hotseatQsbHeight > 0) {
assertThat(dp.isQsbInline).isTrue()
- assertThat(dp.qsbWidth).isGreaterThan(0)
} else { // Launcher3 doesn't have QSB height
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
}
}
@@ -92,14 +91,13 @@
* This test is to make sure that a tablet doesn't inline the QSB if the layout doesn't support
*/
@Test
- fun qsbWidth_is_match_parent_for_tablet_landscape_without_inline() {
+ fun qsb_is_not_inline_for_tablet_landscape_without_inline() {
initializeVarsForTablet(isLandscape = true)
useTwoPanels = true
val dp = newDP()
assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
}
}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
new file mode 100644
index 0000000..31468c5
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.deviceemulator;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+import java.util.concurrent.Callable;
+
+
+public class DisplayEmulator {
+ Context mContext;
+ LauncherInstrumentation mLauncher;
+ DisplayEmulator(Context context, LauncherInstrumentation launcher) {
+ mContext = context;
+ mLauncher = launcher;
+ }
+
+ /**
+ * By changing the WindowManagerProxy we can override the window insets information
+ **/
+ private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
+ WindowManagerProxy.INSTANCE.initializeForTesting(
+ new TestWindowManagerProxy(mContext, deviceData));
+ return WindowManagerGlobal.getWindowManagerService();
+ }
+
+ public <T> T emulate(DeviceEmulationData device, String grid, Callable<T> runInEmulation)
+ throws Exception {
+ WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
+ // Set up emulation
+ final int userId = UserHandle.myUserId();
+ WindowManagerProxy.INSTANCE.initializeForTesting(
+ new TestWindowManagerProxy(mContext, device));
+ IWindowManager wm = changeWindowManagerInstance(device);
+ // Change density twice to force display controller to reset its state
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density, userId);
+ wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, device.width, device.height);
+ wm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1);
+
+ // Set up grid
+ setGrid(grid);
+ try {
+ return runInEmulation.call();
+ } finally {
+ // Clear emulation
+ WindowManagerProxy.INSTANCE.initializeForTesting(original);
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand("cmd window reset");
+ }
+ }
+
+ private void setGrid(String gridType) {
+ // When the grid changes, the desktop arrangement get stored in SQL and we need to wait to
+ // make sure there is no SQL operations running and get SQL_BUSY error, that's why we need
+ // to call mLauncher.waitForLauncherInitialized();
+ mLauncher.waitForLauncherInitialized();
+ String testProviderAuthority = mContext.getPackageName() + ".grid_control";
+ Uri gridUri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(testProviderAuthority)
+ .appendPath("default_grid")
+ .build();
+ ContentValues values = new ContentValues();
+ values.put("name", gridType);
+ mContext.getContentResolver().update(gridUri, values, null, null);
+ }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
new file mode 100644
index 0000000..cbea688
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.deviceemulator;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.WindowInsets;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+import com.android.launcher3.util.RotationUtils;
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+public class TestWindowManagerProxy extends WindowManagerProxy {
+
+ private final DeviceEmulationData mDevice;
+
+ public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
+ super(true);
+ mDevice = device;
+ }
+
+ @Override
+ public boolean isInternalDisplay(Display display) {
+ return display.getDisplayId() == Display.DEFAULT_DISPLAY;
+ }
+
+ @Override
+ protected int getDimenByName(Resources res, String resName) {
+ Integer mock = mDevice.resourceOverrides.get(resName);
+ return mock != null ? mock : super.getDimenByName(res, resName);
+ }
+
+ @Override
+ protected int getDimenByName(Resources res, String resName, String fallback) {
+ return getDimenByName(res, resName);
+ }
+
+ @Override
+ public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
+ int rotation = display.getRotation();
+ Point size = new Point(mDevice.width, mDevice.height);
+ RotationUtils.rotateSize(size, rotation);
+ Rect cutout = new Rect(mDevice.cutout);
+ RotationUtils.rotateRect(cutout, rotation);
+ return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
+ }
+
+ @Override
+ public WindowBounds getRealBounds(Context windowContext, Display display,
+ CachedDisplayInfo info) {
+ return estimateInternalDisplayBounds(windowContext)
+ .get(getDisplayId(display)).second[display.getRotation()];
+ }
+
+ @Override
+ public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
+ Rect outInsets) {
+ outInsets.set(getRealBounds(context, context.getDisplay(),
+ getDisplayInfo(context, context.getDisplay())).insets);
+ return oldInsets;
+ }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
new file mode 100644
index 0000000..8d275cc
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.deviceemulator.models;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.ResourceUtils.getDimenByName;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.ArrayMap;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntArray;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Map;
+
+public class DeviceEmulationData {
+
+ public final int width;
+ public final int height;
+ public final int density;
+ public final String name;
+ public final String[] grids;
+ public final Rect cutout;
+ public final Map<String, Integer> resourceOverrides;
+
+ private static final String[] EMULATED_SYSTEM_RESOURCES = new String[]{
+ NAVBAR_HEIGHT,
+ NAVBAR_HEIGHT_LANDSCAPE,
+ NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE,
+ "status_bar_height",
+ };
+
+ public DeviceEmulationData(int width, int height, int density, Rect cutout, String name,
+ String[] grid,
+ Map<String, Integer> resourceOverrides) {
+ this.width = width;
+ this.height = height;
+ this.density = density;
+ this.name = name;
+ this.grids = grid;
+ this.cutout = cutout;
+ this.resourceOverrides = resourceOverrides;
+ }
+
+ public static DeviceEmulationData deviceFromJSON(JSONObject json) throws JSONException {
+ int width = json.getInt("width");
+ int height = json.getInt("height");
+ int density = json.getInt("density");
+ String name = json.getString("name");
+
+ JSONArray gridArray = json.getJSONArray("grids");
+ String[] grids = new String[gridArray.length()];
+ for (int i = 0, count = grids.length; i < count; i++) {
+ grids[i] = gridArray.getString(i);
+ }
+
+ IntArray deviceCutout = IntArray.fromConcatString(json.getString("cutout"));
+ Rect cutout = new Rect(deviceCutout.get(0), deviceCutout.get(1), deviceCutout.get(2),
+ deviceCutout.get(3));
+
+
+ JSONObject resourceOverridesJson = json.getJSONObject("resourceOverrides");
+ Map<String, Integer> resourceOverrides = new ArrayMap<>();
+ for (String key : resourceOverridesJson.keySet()) {
+ resourceOverrides.put(key, resourceOverridesJson.getInt(key));
+ }
+ return new DeviceEmulationData(width, height, density, cutout, name, grids,
+ resourceOverrides);
+ }
+
+ @Override
+ public String toString() {
+ JSONObject json = new JSONObject();
+ try {
+ json.put("width", width);
+ json.put("height", height);
+ json.put("density", density);
+ json.put("name", name);
+ json.put("cutout", IntArray.wrap(
+ cutout.left, cutout.top, cutout.right, cutout.bottom).toConcatString());
+
+ JSONArray gridArray = new JSONArray();
+ Arrays.stream(grids).forEach(gridArray::put);
+ json.put("grids", gridArray);
+
+
+ JSONObject resourceOverrides = new JSONObject();
+ for (Map.Entry<String, Integer> e : this.resourceOverrides.entrySet()) {
+ resourceOverrides.put(e.getKey(), e.getValue());
+ }
+ json.put("resourceOverrides", resourceOverrides);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return json.toString();
+ }
+
+ public static DeviceEmulationData getCurrentDeviceData(Context context) {
+ DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+ String[] grids = InvariantDeviceProfile.INSTANCE.get(context)
+ .parseAllGridOptions(context).stream()
+ .map(go -> go.name).toArray(String[]::new);
+ String code = Build.MODEL.replaceAll("\\s", "").toLowerCase();
+
+ Map<String, Integer> resourceOverrides = new ArrayMap<>();
+ for (String s : EMULATED_SYSTEM_RESOURCES) {
+ resourceOverrides.put(s, getDimenByName(s, context.getResources(), 0));
+ }
+ return new DeviceEmulationData(info.currentSize.x, info.currentSize.y,
+ info.getDensityDpi(), info.cutout, code, grids, resourceOverrides);
+ }
+
+ public static DeviceEmulationData getDevice(String deviceCode) throws Exception {
+ return DeviceEmulationData.deviceFromJSON(readJSON().getJSONObject(deviceCode));
+ }
+
+ private static JSONObject readJSON() throws Exception {
+ Context context = getInstrumentation().getContext();
+ Resources myRes = context.getResources();
+ int resId = myRes.getIdentifier("devices", "raw", context.getPackageName());
+ try (InputStream is = myRes.openRawResource(resId)) {
+ return new JSONObject(new String(IOUtils.toByteArray(is)));
+ }
+ }
+
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 136f115..6f8b9d2 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -68,6 +68,7 @@
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.LauncherActivityRule;
+import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.util.rule.TestStabilityRule;
@@ -227,7 +228,8 @@
@Rule
public TestRule mOrderSensitiveRules = RuleChain
- .outerRule(new TestStabilityRule())
+ .outerRule(new SamplerRule())
+ .around(new TestStabilityRule())
.around(mActivityMonitor)
.around(getRulesInsideActivityMonitor());
@@ -611,6 +613,10 @@
return createShortcutIfNotExist(name, dimension.x / 2, dimension.y / 2);
}
+ protected HomeAppIcon createShortcutIfNotExist(String name, Point cellPosition) {
+ return createShortcutIfNotExist(name, cellPosition.x, cellPosition.y);
+ }
+
protected HomeAppIcon createShortcutIfNotExist(String name, int cellX, int cellY) {
HomeAppIcon homeAppIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
if (homeAppIcon == null) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f0bef24..a3b05f6 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,6 +18,8 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -46,19 +48,30 @@
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.util.Map;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
private static final String APP_NAME = "LauncherTestApp";
private static final String DUMMY_APP_NAME = "Aardwolf";
+ private static final String MAPS_APP_NAME = "Maps";
+ private static final String STORE_APP_NAME = "Play Store";
+ private static final String GMAIL_APP_NAME = "Gmail";
+
+ @Rule
+ public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
@Before
public void setUp() throws Exception {
@@ -367,28 +380,23 @@
@Test
@PortraitLandscape
- public void testDragToFolder() throws Exception {
- final HomeAppIcon playStoreIcon = createShortcutIfNotExist("Play Store", 0, 1);
- final HomeAppIcon gmailIcon = createShortcutIfNotExist("Gmail", 1, 1);
+ @ScreenRecord
+ public void testDragToFolder() {
+ // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
+ // on tablets or phones due to difference in resolution.
+ final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
+ final HomeAppIcon gmailIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
-
Folder folder = folderIcon.open();
- folder.getAppIcon("Play Store");
- folder.getAppIcon("Gmail");
+ folder.getAppIcon(STORE_APP_NAME);
+ folder.getAppIcon(GMAIL_APP_NAME);
Workspace workspace = folder.close();
- assertNull("Gmail should be moved to a folder.",
- workspace.tryGetWorkspaceAppIcon("Gmail"));
- assertNull("Play Store should be moved to a folder.",
- workspace.tryGetWorkspaceAppIcon("Play Store"));
-
- final HomeAppIcon youTubeIcon = createShortcutInCenterIfNotExist("YouTube");
-
- folderIcon = youTubeIcon.dragToIcon(folderIcon);
- folder = folderIcon.open();
- folder.getAppIcon("YouTube");
- folder.close();
+ assertNull(STORE_APP_NAME + " should be moved to a folder.",
+ workspace.tryGetWorkspaceAppIcon(STORE_APP_NAME));
+ assertNull(GMAIL_APP_NAME + " should be moved to a folder.",
+ workspace.tryGetWorkspaceAppIcon(GMAIL_APP_NAME));
}
@Test
@@ -462,15 +470,7 @@
@Test
@PortraitLandscape
public void testDragAppIconToWorkspaceCell() throws Exception {
- final Point dimensions = mLauncher.getWorkspace().getIconGridDimensions();
-
- Point[] targets = {
- new Point(0, 1),
- new Point(0, dimensions.y - 2),
- new Point(dimensions.x - 1, 1),
- new Point(dimensions.x - 1, dimensions.y - 2),
- new Point(dimensions.x / 2, dimensions.y / 2)
- };
+ Point[] targets = getCornersAndCenterPositions();
for (Point target : targets) {
final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
@@ -491,6 +491,48 @@
}
}
+ @Test
+ public void getIconsPosition_afterIconRemoved_notContained() throws IOException {
+ Point[] gridPositions = getCornersAndCenterPositions();
+ createShortcutIfNotExist(STORE_APP_NAME, gridPositions[0]);
+ createShortcutIfNotExist(MAPS_APP_NAME, gridPositions[1]);
+ TestUtil.installDummyApp();
+ try {
+ createShortcutIfNotExist(DUMMY_APP_NAME, gridPositions[2]);
+ Map<String, Point> initialPositions =
+ mLauncher.getWorkspace().getWorkspaceIconsPositions();
+ assertThat(initialPositions.keySet())
+ .containsAtLeast(DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME);
+
+ mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
+
+ assertNull(
+ DUMMY_APP_NAME + " app was found after being uninstalled",
+ mLauncher.getWorkspace().tryGetWorkspaceAppIcon(DUMMY_APP_NAME));
+
+ Map<String, Point> finalPositions =
+ mLauncher.getWorkspace().getWorkspaceIconsPositions();
+ assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
+ } finally {
+ TestUtil.uninstallDummyApp();
+ }
+ }
+
+ /**
+ * @return List of workspace grid coordinates. Those are not pixels. See {@link
+ * Workspace#getIconGridDimensions()}
+ */
+ private Point[] getCornersAndCenterPositions() {
+ final Point dimensions = mLauncher.getWorkspace().getIconGridDimensions();
+ return new Point[] {
+ new Point(0, 1),
+ new Point(0, dimensions.y - 2),
+ new Point(dimensions.x - 1, 1),
+ new Point(dimensions.x - 1, dimensions.y - 2),
+ new Point(dimensions.x / 2, dimensions.y / 2)
+ };
+ }
+
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index f31e4f3..7c1be1d 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -25,11 +25,14 @@
import android.util.Log;
import android.view.View;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsPagedView;
import com.android.launcher3.allapps.WorkAdapterProvider;
import com.android.launcher3.allapps.WorkEduCard;
+import com.android.launcher3.allapps.WorkPausedCard;
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -38,6 +41,7 @@
import org.junit.Test;
import java.util.Objects;
+import java.util.function.Predicate;
public class WorkProfileTest extends AbstractLauncherUiTest {
@@ -130,6 +134,8 @@
return manager.getCurrentState() == WorkProfileManager.STATE_DISABLED;
}, LauncherInstrumentation.WAIT_TIME_MS);
+ waitForWorkCard("Work paused card not shown", view -> view instanceof WorkPausedCard);
+
// start work profile toggle ON test
executeOnLauncher(l -> {
ActivityAllAppsContainerView<?> allApps = l.getAppsView();
@@ -154,9 +160,19 @@
l.getAppsView().getWorkManager().reset();
});
- waitForLauncherCondition("Work profile education not shown",
- l -> l.getAppsView().getActiveRecyclerView()
- .findViewHolderForAdapterPosition(0).itemView instanceof WorkEduCard,
- LauncherInstrumentation.WAIT_TIME_MS);
+ waitForWorkCard("Work profile education not shown", view -> view instanceof WorkEduCard);
+ }
+
+ private void waitForWorkCard(String message, Predicate<View> workCardCheck) {
+ waitForLauncherCondition(message, l -> {
+ l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+ ViewHolder holder = l.getAppsView().getActiveRecyclerView()
+ .findViewHolderForAdapterPosition(0);
+ try {
+ return holder != null && workCardCheck.test(holder.itemView);
+ } finally {
+ l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+ }
+ }, LauncherInstrumentation.WAIT_TIME_MS);
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 9fb52fc..0db719e 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.ui.widget;
+import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
+
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static org.junit.Assert.assertNotNull;
@@ -144,7 +147,7 @@
// Set callback
PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
- new Intent(mCallbackAction), PendingIntent.FLAG_ONE_SHOT);
+ new Intent(mCallbackAction), FLAG_ONE_SHOT | FLAG_MUTABLE);
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, "setCallback").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", callback));
diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
new file mode 100644
index 0000000..309d055
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [MultiAdditivePropertyFactory] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiAdditivePropertyTest {
+
+ private val received = mutableListOf<Float>()
+
+ private val factory =
+ object : MultiAdditivePropertyFactory<View?>("Test", View.TRANSLATION_X) {
+ override fun apply(obj: View?, value: Float) {
+ received.add(value)
+ }
+ }
+
+ private val p1 = factory.get(1)
+ private val p2 = factory.get(2)
+ private val p3 = factory.get(3)
+
+ @Test
+ fun set_sameIndexes_allApplied() {
+ val v1 = 50f
+ val v2 = 100f
+ p1.set(null, v1)
+ p1.set(null, v1)
+ p1.set(null, v2)
+
+ assertThat(received).containsExactly(v1, v1, v2)
+ }
+
+ @Test
+ fun set_differentIndexes_aggregationApplied() {
+ val v1 = 50f
+ val v2 = 100f
+ val v3 = 150f
+ p1.set(null, v1)
+ p2.set(null, v2)
+ p3.set(null, v3)
+
+ assertThat(received).containsExactly(v1, v1 + v2, v1 + v2 + v3)
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
index 6099987..7d92214 100644
--- a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.launcher3.util
import android.view.View
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 657f213..4c41d7e 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -132,7 +132,9 @@
dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt"));
// Dump bugreport
- if (launcher.getSystemAnomalyMessage(false, false) != null) {
+ final String systemAnomalyMessage = launcher.getSystemAnomalyMessage(false, false);
+ if (systemAnomalyMessage != null) {
+ Log.d(TAG, "Saving bugreport, system anomaly message: " + systemAnomalyMessage, e);
dumpCommand("bugreportz -s", diagFile(description, "Bugreport", "zip"));
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/SamplerRule.java b/tests/src/com/android/launcher3/util/rule/SamplerRule.java
new file mode 100644
index 0000000..6125f2a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/SamplerRule.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.rule;
+
+import android.os.SystemClock;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A rule that generates a file that helps diagnosing cases when the test process was terminated
+ * because the test execution took too long, and tests that ran for too long even without being
+ * terminated. If the process was terminated or the test was long, the test leaves an artifact with
+ * stack traces of all threads, every SAMPLE_INTERVAL_MS. This will help understanding where we
+ * stuck.
+ */
+public class SamplerRule implements TestRule {
+ private static final int TOO_LONG_TEST_MS = 180000;
+ private static final int SAMPLE_INTERVAL_MS = 3000;
+
+ public static Thread startThread(Description description) {
+ Thread thread =
+ new Thread() {
+ @Override
+ public void run() {
+ // Write all-threads stack stace every SAMPLE_INTERVAL_MS while the test
+ // is running.
+ // After the test finishes, delete that file. If the test process is
+ // terminated due to timeout, the trace file won't be deleted.
+ final File file = getFile();
+
+ final long startTime = SystemClock.elapsedRealtime();
+ try (OutputStreamWriter outputStreamWriter =
+ new OutputStreamWriter(
+ new BufferedOutputStream(
+ new FileOutputStream(file)))) {
+ writeSamples(outputStreamWriter);
+ } catch (IOException | InterruptedException e) {
+ // Simply suppressing the exceptions, nothing to do here.
+ } finally {
+ // If the process is not killed, then there was no test timeout, and
+ // we are not interested in the trace file, unless the test ran too
+ // long.
+ if (SystemClock.elapsedRealtime() - startTime < TOO_LONG_TEST_MS) {
+ file.delete();
+ }
+ }
+ }
+
+ private File getFile() {
+ final String strDate = new SimpleDateFormat("HH:mm:ss").format(new Date());
+
+ final String descStr = description.getTestClass().getSimpleName() + "."
+ + description.getMethodName();
+ return artifactFile(
+ "ThreadStackSamples-" + strDate + "-" + descStr + ".txt");
+ }
+
+ private void writeSamples(OutputStreamWriter writer)
+ throws IOException, InterruptedException {
+ int count = 0;
+ while (true) {
+ writer.write(
+ "#"
+ + (count++)
+ + " =============================================\r\n");
+ for (StackTraceElement[] stack : getAllStackTraces().values()) {
+ writer.write("---------------------\r\n");
+ for (StackTraceElement frame : stack) {
+ writer.write(frame.toString() + "\r\n");
+ }
+ }
+ writer.flush();
+
+ sleep(SAMPLE_INTERVAL_MS);
+ }
+ }
+ };
+
+ thread.start();
+ return thread;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ final Thread traceThread = startThread(description);
+ try {
+ base.evaluate();
+ } finally {
+ traceThread.interrupt();
+ traceThread.join();
+ }
+ }
+ };
+ }
+
+ private static File artifactFile(String fileName) {
+ return new File(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir(),
+ fileName);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
new file mode 100644
index 0000000..5f92199
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+/**
+ * Operations on home screen qsb.
+ */
+public class HomeQsb {
+
+ private final LauncherInstrumentation mLauncher;
+
+ HomeQsb(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
+ mLauncher.waitForLauncherObject("search_container_hotseat");
+ }
+
+ /**
+ * Show search result page from tapping qsb.
+ */
+ public SearchResultFromQsb showSearchResult() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to open search result page");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.clickLauncherObject(
+ mLauncher.waitForLauncherObject("search_container_hotseat"));
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "clicked qsb to open search result page")) {
+ return new SearchResultFromQsb(mLauncher);
+ }
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 45a0196..91ab1bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,8 +16,6 @@
package com.android.launcher3.tapl;
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
import android.graphics.Point;
@@ -33,7 +31,7 @@
/**
* Ancestor for AppIcon and AppMenuItem.
*/
-abstract class Launchable {
+public abstract class Launchable {
protected static final int DEFAULT_DRAG_STEPS = 10;
@@ -64,35 +62,25 @@
protected abstract String launchableType();
private LaunchedAppState launch(BySelector selector) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"want to launch an app from " + launchableType())) {
LauncherInstrumentation.log("Launchable.launch before click "
+ mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
final String label = mObject.getText();
- executeAndWaitForWindowChange(() -> {
- mLauncher.clickLauncherObject(mObject);
- expectActivityStartEvents();
- }, label, "clicking " + launchableType());
+ mLauncher.clickLauncherObject(mObject);
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("clicked")) {
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+ expectActivityStartEvents();
return assertAppLaunched(label, selector);
}
}
}
- protected void executeAndWaitForWindowChange(Runnable command, String label, String action) {
- mLauncher.executeAndWaitForEvent(
- command,
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Launching an app didn't open a new window: " + label,
- action);
- }
-
protected LaunchedAppState assertAppLaunched(String label, BySelector selector) {
mLauncher.assertTrue(
"App didn't start: " + label + " (" + selector + ")",
- TestHelpers.wait(Until.hasObject(selector),
+ mLauncher.getDevice().wait(Until.hasObject(selector),
LauncherInstrumentation.WAIT_TIME_MS));
return new LaunchedAppState(mLauncher);
}
@@ -117,7 +105,6 @@
expectLongClickEvents);
}
-
return dragStartCenter;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 2033a42..face02a 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -129,42 +129,25 @@
try (LauncherInstrumentation.Closable c3 = launcher.addContextLayer(
"moved pointer to drop point")) {
- dropDraggedItem(
- launcher,
- launchable,
- expectedNewPackageName,
- endPoint, downTime,
- itemVisibleCenter,
- itemVisibleBounds,
- itemLabel,
- expectedExistingPackageName);
+ LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
+ + "before drop " + itemVisibleCenter + " in " + itemVisibleBounds);
+ launcher.sendPointer(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ endPoint,
+ LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
+ LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
+ + "after drop");
+
+ try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer(
+ "dropped item")) {
+ launchable.assertAppLaunched(itemLabel, By.pkg(expectedNewPackageName));
+ launchable.assertAppLaunched(
+ itemLabel, By.pkg(expectedExistingPackageName));
+ }
}
}
}
}
-
- private static void dropDraggedItem(
- LauncherInstrumentation launcher, Launchable launchable, String expectedNewPackageName,
- Point endPoint, long downTime, Point itemVisibleCenter, Rect itemVisibleBounds,
- String itemLabel, String expectedExistingPackageName) {
- LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen before drop "
- + itemVisibleCenter + " in " + itemVisibleBounds);
-
- launchable.executeAndWaitForWindowChange(() -> {
- launcher.sendPointer(
- downTime,
- SystemClock.uptimeMillis(),
- MotionEvent.ACTION_UP,
- endPoint,
- LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
- LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: after "
- + "drop");
- }, itemLabel, "dropping taskbar item");
-
- try (LauncherInstrumentation.Closable c = launcher.addContextLayer("dropped item")) {
- launchable.assertAppLaunched(itemLabel, By.pkg(expectedNewPackageName));
- launcher.checkPackagesVisible(
- new String[] {expectedNewPackageName, expectedExistingPackageName});
- }
- }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 950e72c..9d25b1b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -84,7 +84,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
@@ -92,7 +91,6 @@
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -227,11 +225,6 @@
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
- try {
- mDevice.executeShellCommand("am wait-for-broadcast-idle");
- } catch (IOException e) {
- log("Failed to wait for broadcast idle");
- }
// Launcher should run in test harness so that custom accessibility protocol between
// Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
@@ -506,29 +499,16 @@
}
}
- void checkPackagesVisible(String[] expectedVisiblePackages) {
- Set<String> actualVisiblePackages =
- getVisiblePackagesStream().collect(Collectors.toSet());
-
- for (String expectedVisible : expectedVisiblePackages) {
- assertTrue("Expected package not visible: " + expectedVisible,
- actualVisiblePackages.contains(expectedVisible));
- }
- }
-
private String getVisiblePackages() {
- final String apps = getVisiblePackagesStream().collect(Collectors.joining(", "));
- return !apps.isEmpty()
- ? "active app: " + apps
- : "the test doesn't see views from any app, including Launcher";
- }
-
- private Stream<String> getVisiblePackagesStream() {
- return mDevice.findObjects(getAnyObjectSelector())
+ final String apps = mDevice.findObjects(getAnyObjectSelector())
.stream()
.map(LauncherInstrumentation::getApplicationPackageSafe)
.distinct()
- .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg));
+ .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
+ .collect(Collectors.joining(", "));
+ return !apps.isEmpty()
+ ? "active app: " + apps
+ : "the test doesn't see views from any app, including Launcher";
}
private static String getApplicationPackageSafe(UiObject2 object) {
@@ -1504,9 +1484,14 @@
0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
}
+ private boolean hasTIS() {
+ return getTestInfo(TestProtocol.REQUEST_HAS_TIS).getBoolean(TestProtocol.REQUEST_HAS_TIS);
+ }
+
+
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope) {
- final boolean notLauncher3 = !isLauncher3();
+ final boolean hasTIS = hasTIS();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
@@ -1514,12 +1499,12 @@
&& gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
}
- if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
+ if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
}
break;
case MotionEvent.ACTION_UP:
- if (notLauncher3 && gestureScope != GestureScope.INSIDE
+ if (hasTIS && gestureScope != GestureScope.INSIDE
&& gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
&& (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
|| gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
@@ -1533,7 +1518,7 @@
|| gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
}
- if (notLauncher3 && getNavigationModel() != NavigationModel.THREE_BUTTON) {
+ if (hasTIS && getNavigationModel() != NavigationModel.THREE_BUTTON) {
expectEvent(TestProtocol.SEQUENCE_TIS,
gestureScope == GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
|| gestureScope == GestureScope.OUTSIDE_WITH_KEYCODE
@@ -1567,11 +1552,11 @@
// vx0: initial speed at the x-dimension, set as twice the avg speed
// dx: the constant deceleration at the x-dimension
- double vx0 = 2 * (to.x - from.x) / duration;
+ double vx0 = 2.0 * (to.x - from.x) / duration;
double dx = vx0 / duration;
// vy0: initial speed at the y-dimension, set as twice the avg speed
// dy: the constant deceleration at the y-dimension
- double vy0 = 2 * (to.y - from.y) / duration;
+ double vy0 = 2.0 * (to.y - from.y) / duration;
double dy = vy0 / duration;
for (long i = 0; i < steps; ++i) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
deleted file mode 100644
index 42b6bc9..0000000
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.tapl;
-
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.testing.TestProtocol;
-
-public class OptionsPopupMenuItem {
-
- private final LauncherInstrumentation mLauncher;
- private final UiObject2 mObject;
-
- OptionsPopupMenuItem(@NonNull LauncherInstrumentation launcher, @NonNull UiObject2 shortcut) {
- mLauncher = launcher;
- mObject = shortcut;
- }
-
- /**
- * Clicks the option.
- */
- @NonNull
- public void launch(@NonNull String expectedPackageName) {
- try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- LauncherInstrumentation.log("OptionsPopupMenuItem before click "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
- mLauncher.clickLauncherObject(mObject);
- mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
- mLauncher.assertTrue(
- "App didn't start: " + By.pkg(expectedPackageName),
- mLauncher.getDevice().wait(Until.hasObject(By.pkg(expectedPackageName)),
- LauncherInstrumentation.WAIT_TIME_MS));
- }
- }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
new file mode 100644
index 0000000..82652c7
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import android.widget.TextView;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Operations on search result page opened from home screen qsb.
+ */
+public class SearchResultFromQsb {
+ // The input resource id in the search box.
+ private static final String INPUT_RES = "input";
+ private final LauncherInstrumentation mLauncher;
+
+ SearchResultFromQsb(LauncherInstrumentation launcher) {
+ mLauncher = launcher;
+ mLauncher.waitForLauncherObject("search_container_all_apps");
+ }
+
+ /** Set the input to the search input edit text and update search results. */
+ public void searchForInput(String input) {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to search for result with an input");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.waitForLauncherObject(INPUT_RES).setText(input);
+ }
+ }
+
+ /** Find the app from search results with app name. */
+ public Launchable findAppIcon(String appName) {
+ UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+ return new AllAppsAppIcon(mLauncher, icon);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index fee4490..954af3d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -43,6 +43,7 @@
import com.android.launcher3.testing.WorkspaceCellCenterRequest;
import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -111,6 +112,18 @@
}
/**
+ * Returns the home qsb.
+ *
+ * The qsb must already be visible when calling this method.
+ */
+ public HomeQsb getQsb() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to get the home qsb")) {
+ return new HomeQsb(mLauncher);
+ }
+ }
+
+ /**
* Returns an icon for the app, if currently visible.
*
* @param appName name of the app
@@ -222,6 +235,21 @@
mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
}
+ /**
+ * @return map of text -> center of the view. In case of icons with the same name, the one with
+ * lower x coordinate is selected.
+ */
+ public Map<String, Point> getWorkspaceIconsPositions() {
+ final UiObject2 workspace = verifyActiveContainer();
+ List<UiObject2> workspaceIcons =
+ mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
+ return workspaceIcons.stream()
+ .collect(
+ Collectors.toMap(
+ /* keyMapper= */ UiObject2::getText,
+ /* valueMapper= */ UiObject2::getVisibleCenter,
+ /* mergeFunction= */ (p1, p2) -> p1.x < p2.x ? p1 : p2));
+ }
/*
* Get the center point of the delete/uninstall icon in the drop target bar.
*/
@@ -389,8 +417,7 @@
// Since the destination can be on another page, we need to drag to the edge first
// until we reach the target page
while (targetDest.x > displayX || targetDest.x < 0) {
- // TODO: b/219919285
- int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
+ int edgeX = targetDest.x > 0 ? displayX : 0;
Point screenEdge = new Point(edgeX, targetDest.y);
Point finalDragStart = dragStart;
executeAndWaitForPageScroll(launcher,